[automerger skipped] Merge "Annotate GpuWorkTracepointTest with RequiresDevice" into android15-tests-dev am: 88b781fbf1 -s ours

am skip reason: Merged-In I57f5f035a6ea7ac12e6fb6c619385af75defda2a with SHA-1 9f249a4139 is already in history

Original change: https://android-review.googlesource.com/c/platform/frameworks/native/+/3459750

Change-Id: I45f81331999b045e11e2c6bfd57f5178e7aadb4c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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/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/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 6576ffd..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");
 
@@ -1758,6 +1788,8 @@
 
     DoKmsg();
 
+    DumpKernelMemoryAllocations();
+
     DumpShutdownCheckpoints();
 
     DumpIpAddrAndRules();
@@ -1783,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"});
 
@@ -2429,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,
@@ -4626,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/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 9384926..b7ad331 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -60,7 +60,7 @@
 }
 
 PR_DEXOPT_JOB_VERSION="$(pm art pr-dexopt-job --version)"
-if (( $? == 0 )) && (( $PR_DEXOPT_JOB_VERSION >= 2 )); then
+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
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 1333599..59c4d53 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -29,6 +29,7 @@
 #include <thread>
 
 #if !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+#include "perfetto/public/protos/trace/android/android_track_event.pzc.h"
 #include "perfetto/public/te_category_macros.h"
 #include "perfetto/public/te_macros.h"
 #endif // !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
@@ -57,6 +58,12 @@
 #define SM_PERFETTO_TRACE_FUNC(...) \
     PERFETTO_TE_SCOPED(servicemanager, PERFETTO_TE_SLICE_BEGIN(__func__) __VA_OPT__(, ) __VA_ARGS__)
 
+constexpr uint32_t kProtoServiceName =
+        perfetto_protos_AndroidTrackEvent_binder_service_name_field_number;
+constexpr uint32_t kProtoInterfaceName =
+        perfetto_protos_AndroidTrackEvent_binder_interface_name_field_number;
+constexpr uint32_t kProtoApexName = perfetto_protos_AndroidTrackEvent_apex_name_field_number;
+
 #endif // !(defined(VENDORSERVICEMANAGER) || defined(__ANDROID_RECOVERY__))
 
 bool is_multiuser_uid_isolated(uid_t uid) {
@@ -112,13 +119,15 @@
     std::string iface;
     std::string instance;
 
-    static bool fill(const std::string& name, AidlName* aname) {
+    static bool fill(const std::string& name, AidlName* aname, bool logError) {
         size_t firstSlash = name.find('/');
         size_t lastDot = name.rfind('.', firstSlash);
         if (firstSlash == std::string::npos || lastDot == std::string::npos) {
-            ALOGE("VINTF HALs require names in the format type/instance (e.g. "
-                  "some.package.foo.IFoo/default) but got: %s",
-                  name.c_str());
+            if (logError) {
+                ALOGE("VINTF HALs require names in the format type/instance (e.g. "
+                      "some.package.foo.IFoo/default) but got: %s",
+                      name.c_str());
+            }
             return false;
         }
         aname->package = name.substr(0, lastDot);
@@ -151,7 +160,7 @@
     }
 
     AidlName aname;
-    if (!AidlName::fill(name, &aname)) return false;
+    if (!AidlName::fill(name, &aname, true)) return false;
 
     bool found = forEachManifest([&](const ManifestWithDescription& mwd) {
         if (mwd.manifest->hasAidlInstance(aname.package, aname.iface, aname.instance)) {
@@ -209,7 +218,7 @@
     }
 
     AidlName aname;
-    if (!AidlName::fill(name, &aname)) return std::nullopt;
+    if (!AidlName::fill(name, &aname, true)) return std::nullopt;
 
     std::optional<std::string> updatableViaApex;
 
@@ -249,9 +258,28 @@
     return names;
 }
 
+static std::optional<std::string> getVintfAccessorName(const std::string& name) {
+    AidlName aname;
+    if (!AidlName::fill(name, &aname, false)) return std::nullopt;
+
+    std::optional<std::string> accessor;
+    forEachManifest([&](const ManifestWithDescription& mwd) {
+        mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+            if (manifestInstance.format() != vintf::HalFormat::AIDL) return true;
+            if (manifestInstance.package() != aname.package) return true;
+            if (manifestInstance.interface() != aname.iface) return true;
+            if (manifestInstance.instance() != aname.instance) return true;
+            accessor = manifestInstance.accessor();
+            return false; // break (libvintf uses opposite convention)
+        });
+        return false; // continue
+    });
+    return accessor;
+}
+
 static std::optional<ConnectionInfo> getVintfConnectionInfo(const std::string& name) {
     AidlName aname;
-    if (!AidlName::fill(name, &aname)) return std::nullopt;
+    if (!AidlName::fill(name, &aname, true)) return std::nullopt;
 
     std::optional<std::string> ip;
     std::optional<uint64_t> port;
@@ -365,23 +393,63 @@
 }
 
 Status ServiceManager::getService(const std::string& name, sp<IBinder>* outBinder) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
-    *outBinder = tryGetService(name, true);
+    *outBinder = tryGetBinder(name, true).service;
+    // returns ok regardless of result for legacy reasons
+    return Status::ok();
+}
+
+Status ServiceManager::getService2(const std::string& name, os::Service* outService) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
+
+    *outService = tryGetService(name, true);
     // returns ok regardless of result for legacy reasons
     return Status::ok();
 }
 
 Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
-    *outBinder = tryGetService(name, false);
+    *outBinder = tryGetBinder(name, false).service;
     // returns ok regardless of result for legacy reasons
     return Status::ok();
 }
 
-sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+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())));
+
+    *outService = tryGetService(name, false);
+    // returns ok regardless of result for legacy reasons
+    return Status::ok();
+}
+
+os::Service ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
+    std::optional<std::string> accessorName;
+#ifndef VENDORSERVICEMANAGER
+    accessorName = getVintfAccessorName(name);
+#endif
+    if (accessorName.has_value()) {
+        auto ctx = mAccess->getCallingContext();
+        if (!mAccess->canFind(ctx, name)) {
+            return os::Service::make<os::Service::Tag::accessor>(nullptr);
+        }
+        return os::Service::make<os::Service::Tag::accessor>(
+                tryGetBinder(*accessorName, startIfNotFound).service);
+    } else {
+        return os::Service::make<os::Service::Tag::serviceWithMetadata>(
+                tryGetBinder(name, 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())));
 
     auto ctx = mAccess->getCallingContext();
 
@@ -393,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) {
@@ -416,12 +484,16 @@
         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) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     if (name.size() == 0) return false;
     if (name.size() > 127) return false;
@@ -438,7 +510,8 @@
 }
 
 Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
@@ -446,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) {
@@ -561,12 +635,16 @@
 
 Status ServiceManager::registerForNotifications(
         const std::string& name, const sp<IServiceCallback>& callback) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
-    if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux");
+    // TODO(b/338541373): Implement the notification mechanism for services accessed via
+    // IAccessor.
+    std::optional<std::string> accessorName;
+    if (auto status = canFindService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     // note - we could allow isolated apps to get notifications if we
@@ -609,12 +687,14 @@
 }
 Status ServiceManager::unregisterForNotifications(
         const std::string& name, const sp<IServiceCallback>& callback) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
-    if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canFindService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     bool found = false;
@@ -634,12 +714,14 @@
 }
 
 Status ServiceManager::isDeclared(const std::string& name, bool* outReturn) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
-    if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canFindService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     *outReturn = false;
@@ -651,7 +733,8 @@
 }
 
 binder::Status ServiceManager::getDeclaredInstances(const std::string& interface, std::vector<std::string>* outReturn) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("interface", interface.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoInterfaceName, interface.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
@@ -662,8 +745,10 @@
 
     outReturn->clear();
 
+    std::optional<std::string> _accessorName;
     for (const std::string& instance : allInstances) {
-        if (mAccess->canFind(ctx, interface + "/" + instance)) {
+        if (auto status = canFindService(ctx, interface + "/" + instance, &_accessorName);
+            status.isOk()) {
             outReturn->push_back(instance);
         }
     }
@@ -677,12 +762,14 @@
 
 Status ServiceManager::updatableViaApex(const std::string& name,
                                         std::optional<std::string>* outReturn) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
-    if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> _accessorName;
+    if (auto status = canFindService(ctx, name, &_accessorName); !status.isOk()) {
+        return status;
     }
 
     *outReturn = std::nullopt;
@@ -695,7 +782,8 @@
 
 Status ServiceManager::getUpdatableNames([[maybe_unused]] const std::string& apexName,
                                          std::vector<std::string>* outReturn) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("apexName", apexName.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoApexName, apexName.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
@@ -706,8 +794,9 @@
 
     outReturn->clear();
 
+    std::optional<std::string> _accessorName;
     for (const std::string& name : apexUpdatableNames) {
-        if (mAccess->canFind(ctx, name)) {
+        if (auto status = canFindService(ctx, name, &_accessorName); status.isOk()) {
             outReturn->push_back(name);
         }
     }
@@ -720,12 +809,14 @@
 
 Status ServiceManager::getConnectionInfo(const std::string& name,
                                          std::optional<ConnectionInfo>* outReturn) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     auto ctx = mAccess->getCallingContext();
 
-    if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> _accessorName;
+    if (auto status = canFindService(ctx, name, &_accessorName); !status.isOk()) {
+        return status;
     }
 
     *outReturn = std::nullopt;
@@ -804,15 +895,17 @@
 
 Status ServiceManager::registerClientCallback(const std::string& name, const sp<IBinder>& service,
                                               const sp<IClientCallback>& cb) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     if (cb == nullptr) {
         return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Callback null.");
     }
 
     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);
@@ -966,15 +1059,17 @@
 }
 
 Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IBinder>& binder) {
-    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
     if (binder == nullptr) {
         return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Null service.");
     }
 
     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);
@@ -1032,6 +1127,40 @@
     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)) {
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied for service.");
+    }
+#ifndef VENDORSERVICEMANAGER
+    *accessor = getVintfAccessorName(name);
+#endif
+    if (accessor->has_value()) {
+        if (!mAccess->canFind(ctx, accessor->value())) {
+            return Status::fromExceptionCode(Status::EX_SECURITY,
+                                             "SELinux denied for the accessor of the service.");
+        }
+    }
+    return Status::ok();
+}
+
 Status ServiceManager::getServiceDebugInfo(std::vector<ServiceDebugInfo>* outReturn) {
     SM_PERFETTO_TRACE_FUNC();
     if (!mAccess->canList(mAccess->getCallingContext())) {
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 1536014..5c4d891 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -45,7 +45,9 @@
 
     // 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, 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;
@@ -112,7 +114,12 @@
     // this updates the iterator to the next location
     void removeClientCallback(const wp<IBinder>& who, ClientCallbackMap::iterator* it);
 
-    sp<IBinder> tryGetService(const std::string& name, bool startIfNotFound);
+    os::Service tryGetService(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);
 
     ServiceMap mNameToService;
     ServiceCallbackMap mNameToRegistrationCallback;
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 b575053..7ad84fa 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -38,6 +38,7 @@
 using android::binder::Status;
 using android::os::BnServiceCallback;
 using android::os::IServiceManager;
+using android::os::Service;
 using testing::_;
 using testing::ElementsAre;
 using testing::NiceMock;
@@ -91,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) {
@@ -153,18 +159,24 @@
     EXPECT_TRUE(sm->addService("foo", serviceA, false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
-    sp<IBinder> outA;
-    EXPECT_TRUE(sm->getService("foo", &outA).isOk());
-    EXPECT_EQ(serviceA, outA);
+    Service outA;
+    EXPECT_TRUE(sm->getService2("foo", &outA).isOk());
+    EXPECT_EQ(serviceA, outA.get<Service::Tag::serviceWithMetadata>().service);
+    sp<IBinder> outBinderA;
+    EXPECT_TRUE(sm->getService("foo", &outBinderA).isOk());
+    EXPECT_EQ(serviceA, outBinderA);
 
     // serviceA should be overwritten by serviceB
     sp<IBinder> serviceB = getBinder();
     EXPECT_TRUE(sm->addService("foo", serviceB, false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
-    sp<IBinder> outB;
-    EXPECT_TRUE(sm->getService("foo", &outB).isOk());
-    EXPECT_EQ(serviceB, outB);
+    Service outB;
+    EXPECT_TRUE(sm->getService2("foo", &outB).isOk());
+    EXPECT_EQ(serviceB, outB.get<Service::Tag::serviceWithMetadata>().service);
+    sp<IBinder> outBinderB;
+    EXPECT_TRUE(sm->getService("foo", &outBinderB).isOk());
+    EXPECT_EQ(serviceB, outBinderB);
 }
 
 TEST(AddService, NoPermissions) {
@@ -186,17 +198,28 @@
     EXPECT_TRUE(sm->addService("foo", service, false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
-    sp<IBinder> out;
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
-    EXPECT_EQ(service, out);
+    Service out;
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
+    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) {
     auto sm = getPermissiveServiceManager();
 
-    sp<IBinder> out;
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
-    EXPECT_EQ(nullptr, out.get());
+    Service out;
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
+    EXPECT_EQ(nullptr, out.get<Service::Tag::serviceWithMetadata>().service);
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(nullptr, outBinder);
 }
 
 TEST(GetService, NoPermissionsForGettingService) {
@@ -204,31 +227,37 @@
 
     EXPECT_CALL(*access, getCallingContext()).WillRepeatedly(Return(Access::CallingContext{}));
     EXPECT_CALL(*access, canAdd(_, _)).WillOnce(Return(true));
-    EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*access, canFind(_, _)).WillRepeatedly(Return(false));
 
     sp<ServiceManager> sm = sp<NiceMock<MockServiceManager>>::make(std::move(access));
 
     EXPECT_TRUE(sm->addService("foo", getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
-    sp<IBinder> out;
+    Service out;
     // returns nullptr but has OK status for legacy compatibility
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
-    EXPECT_EQ(nullptr, out.get());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
+    EXPECT_EQ(nullptr, out.get<Service::Tag::serviceWithMetadata>().service);
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(nullptr, outBinder);
 }
 
 TEST(GetService, AllowedFromIsolated) {
     std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
 
     EXPECT_CALL(*access, getCallingContext())
-        // something adds it
-        .WillOnce(Return(Access::CallingContext{}))
-        // next call is from isolated app
-        .WillOnce(Return(Access::CallingContext{
-            .uid = AID_ISOLATED_START,
-        }));
+            // something adds it
+            .WillOnce(Return(Access::CallingContext{}))
+            // next calls is from isolated app
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }))
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }));
     EXPECT_CALL(*access, canAdd(_, _)).WillOnce(Return(true));
-    EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(*access, canFind(_, _)).WillRepeatedly(Return(true));
 
     sp<ServiceManager> sm = sp<NiceMock<MockServiceManager>>::make(std::move(access));
 
@@ -236,21 +265,27 @@
     EXPECT_TRUE(sm->addService("foo", service, true /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
-    sp<IBinder> out;
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
-    EXPECT_EQ(service, out.get());
+    Service out;
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
+    EXPECT_EQ(service, out.get<Service::Tag::serviceWithMetadata>().service);
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(service, outBinder);
 }
 
 TEST(GetService, NotAllowedFromIsolated) {
     std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
 
     EXPECT_CALL(*access, getCallingContext())
-        // something adds it
-        .WillOnce(Return(Access::CallingContext{}))
-        // next call is from isolated app
-        .WillOnce(Return(Access::CallingContext{
-            .uid = AID_ISOLATED_START,
-        }));
+            // something adds it
+            .WillOnce(Return(Access::CallingContext{}))
+            // next calls is from isolated app
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }))
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }));
     EXPECT_CALL(*access, canAdd(_, _)).WillOnce(Return(true));
 
     // TODO(b/136023468): when security check is first, this should be called first
@@ -261,10 +296,13 @@
     EXPECT_TRUE(sm->addService("foo", getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
-    sp<IBinder> out;
+    Service out;
     // returns nullptr but has OK status for legacy compatibility
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
-    EXPECT_EQ(nullptr, out.get());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
+    EXPECT_EQ(nullptr, out.get<Service::Tag::serviceWithMetadata>().service);
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(nullptr, outBinder);
 }
 
 TEST(ListServices, NoPermissions) {
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/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 c3f2fed..f593eda 100644
--- a/data/etc/input/motion_predictor_config.xml
+++ b/data/etc/input/motion_predictor_config.xml
@@ -35,7 +35,11 @@
 
     The jerk thresholds are based on normalized dt = 1 calculations.
   -->
-  <low-jerk>1.0</low-jerk>
-  <high-jerk>1.1</high-jerk>
+  <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 fad8c1b..9dfa279 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -3,3 +3,11 @@
 # 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/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 97e4dc0..3486e9b 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -52,7 +52,7 @@
  *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
  */
 
-#include <android/api-level.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <unistd.h>
 
@@ -84,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
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 099a2bc..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
@@ -251,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.
  *
@@ -353,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,
@@ -361,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
@@ -373,6 +429,28 @@
                                   float b, float alpha, enum ADataSpace dataspace)
         __INTRODUCED_IN(29);
 
+// These APIs (setGeometry and setCrop) were originally written in a
+// C-incompatible form using references instead of pointers, and the OS shipped
+// that version for years before it was noticed. Fortunately the compiled code
+// for callers is the same regardless of whether it's a pointer or a reference,
+// so we can declare this as a nonnull pointer for C and keep the existing C++
+// decl and definition.
+//
+// We could alternatively change the decl and the definition to both be a
+// pointer (with an inline definition using references to preserve source compat
+// for existing C++ callers), but that requires changing the definition of an
+// API that has been in the OS for years. It's theoretically a safe change, but
+// without being able to prove it that's a very big risk to take. By keeping the
+// C-compatibility hack in the header, we can be sure that we haven't changed
+// anything for existing callers. By definition there were no C users of the
+// reference-based decl; if there were any C callers of the API at all, they were
+// using the same workaround that is now used below.
+//
+// Even if this workaround turns out to not work for C, there's no permanent
+// damage done to the platform (unlike if we were to change the definition). At
+// worst it continues to work for C++ (since the preprocessed header as seen by
+// C++ hasn't changed, nor has the definition) and continues to not work for C.
+
 /**
  * \param source The sub-rect within the buffer's content to be rendered inside the surface's area
  * The surface's source rect is clipped by the bounds of its current buffer. The source rect's width
@@ -383,7 +461,7 @@
  * clipped by the bounds of its parent. The destination rect's width and height must be > 0.
  *
  * \param transform The transform applied after the source rect is applied to the buffer. This
- * parameter should be set to 0 for no transform. To specify a transfrom use the
+ * parameter should be set to 0 for no transform. To specify a transform use the
  * NATIVE_WINDOW_TRANSFORM_* enum.
  *
  * Available since API level 29.
@@ -394,9 +472,14 @@
  * properties at once.
  */
 void ASurfaceTransaction_setGeometry(ASurfaceTransaction* _Nonnull transaction,
-                                     ASurfaceControl* _Nonnull surface_control, const ARect& source,
-                                     const ARect& destination, int32_t transform)
-        __INTRODUCED_IN(29);
+                                     ASurfaceControl* _Nonnull surface_control,
+#if defined(__cplusplus)
+                                     const ARect& source, const ARect& destination,
+#else
+                                     const ARect* _Nonnull source,
+                                     const ARect* _Nonnull destination,
+#endif
+                                     int32_t transform) __INTRODUCED_IN(29);
 
 /**
  * Bounds the surface and its children to the bounds specified. The crop and buffer size will be
@@ -408,7 +491,12 @@
  * Available since API level 31.
  */
 void ASurfaceTransaction_setCrop(ASurfaceTransaction* _Nonnull transaction,
-                                 ASurfaceControl* _Nonnull surface_control, const ARect& crop)
+                                 ASurfaceControl* _Nonnull surface_control,
+#if defined(__cplusplus)
+                                 const ARect& crop)
+#else
+                                 const ARect* _Nonnull crop)
+#endif
         __INTRODUCED_IN(31);
 
 /**
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
index bdc5249..f0503f6 100644
--- a/include/android/surface_control_input_receiver.h
+++ b/include/android/surface_control_input_receiver.h
@@ -59,17 +59,13 @@
                                           AInputEvent *_Nonnull keyEvent)
                                           __INTRODUCED_IN(__ANDROID_API_V__);
 
-struct AInputReceiverCallbacks;
-
-struct AInputReceiver;
+typedef struct AInputReceiverCallbacks AInputReceiverCallbacks;
 
 /**
  * The InputReceiver that holds the reference to the registered input channel. This must be released
  * using AInputReceiver_release
- *
- * Available since API level 35.
  */
-typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__);
+typedef struct AInputReceiver AInputReceiver;
 
 /**
  * Registers an input receiver for an ASurfaceControl that will receive batched input event. For
diff --git a/include/android/thermal.h b/include/android/thermal.h
index fa168cd..1d7ad12 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -85,6 +85,7 @@
     /** Need shutdown immediately. */
     ATHERMAL_STATUS_SHUTDOWN = 6,
 };
+typedef enum AThermalStatus AThermalStatus;
 
 /**
  * An opaque type representing a handle to a thermal manager.
@@ -240,6 +241,7 @@
     float headroom;
     AThermalStatus thermalStatus;
 };
+typedef struct AThermalHeadroomThreshold AThermalHeadroomThreshold;
 
 /**
  * Gets the thermal headroom thresholds for all available thermal status.
@@ -252,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.
@@ -260,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/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/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/input/Input.h b/include/input/Input.h
index ec08cdd..a8684bd 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -25,6 +25,7 @@
 #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>
@@ -69,15 +70,17 @@
      * actual intent.
      */
     AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+            static_cast<int32_t>(android::os::MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED),
+
     AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
-            android::os::IInputConstants::MOTION_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 =
-            android::os::IInputConstants::MOTION_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
@@ -86,27 +89,27 @@
      * into focus.
      */
     AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+            static_cast<int32_t>(android::os::MotionEventFlag::NO_FOCUS_CHANGE),
 
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_ACCESSIBILITY_EVENT),
 
     AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
-            android::os::IInputConstants::MOTION_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 = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
+    AMOTION_EVENT_FLAG_TAINTED = static_cast<int32_t>(android::os::MotionEventFlag::TAINTED),
 
     /** Private flag, not used in Java. */
     AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION =
-            android::os::IInputConstants::MOTION_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 = android::os::IInputConstants::
-            MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
+    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 |
@@ -193,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 {
@@ -241,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);
@@ -890,9 +902,7 @@
     void splitFrom(const MotionEvent& other, std::bitset<MAX_POINTER_ID + 1> splitPointerIds,
                    int32_t newEventId);
 
-    void addSample(
-            nsecs_t eventTime,
-            const PointerCoords* pointerCoords);
+    void addSample(nsecs_t eventTime, const PointerCoords* pointerCoords, int32_t eventId);
 
     void offsetLocation(float xOffset, float yOffset);
 
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 9e48b08..c98b9cf 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -16,15 +16,21 @@
 
 #pragma once
 
+#include <map>
+#include <memory>
+#include <optional>
+
+#include <input/Input.h>
+#include <input/InputTransport.h>
+#include <input/Resampler.h>
 #include <utils/Looper.h>
-#include "InputTransport.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.
+ * handle the incoming input. The events received by these callbacks are never null.
  */
 class InputConsumerCallbacks {
 public:
@@ -34,7 +40,7 @@
     /**
      * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
      * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
-     * std::nullopt frameTime to receive the pending motion event(s).
+     * 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;
@@ -47,13 +53,13 @@
 /**
  * Consumes input events from an input channel.
  *
- * This is a re-implementation of InputConsumer that does not have resampling at the current moment.
- * 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.
+ * 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 to this class
+ * - 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
@@ -64,8 +70,18 @@
  */
 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);
+                                       sp<Looper> looper, InputConsumerCallbacks& callbacks,
+                                       std::unique_ptr<Resampler> resampler);
+
     ~InputConsumerNoResampling();
 
     /**
@@ -74,15 +90,17 @@
     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), the you still must call
-     * this. For frameTime, use a std::nullopt.
-     * @param frameTime 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.
+     * 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> frameTime);
+    bool consumeBatchedInputEvents(std::optional<nsecs_t> requestedFrameTime);
+
     /**
      * Returns true when there is *likely* a pending batch or a pending event in the channel.
      *
@@ -99,6 +117,7 @@
     std::shared_ptr<InputChannel> mChannel;
     sp<Looper> mLooper;
     InputConsumerCallbacks& mCallbacks;
+    std::unique_ptr<Resampler> mResampler;
 
     // Looper-related infrastructure
     /**
@@ -177,11 +196,34 @@
     /**
      * 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 frameTime) when the consumer calls
+     * 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
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 7d8c19e..1a48239 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -389,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 25d35e9..5bd5070 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -19,6 +19,8 @@
 #include <android/input.h>
 #include <attestation/HmacKeyManager.h>
 #include <input/Input.h>
+#include <input/InputTransport.h>
+#include <ui/LogicalDisplayId.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
 #include <vector>
@@ -44,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;
@@ -58,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) {
@@ -127,7 +215,7 @@
         return *this;
     }
 
-    MotionEvent build() {
+    MotionEvent build() const {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
         for (const PointerBuilder& pointer : mPointers) {
@@ -135,20 +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;
         event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
                          mAction, mActionButton, mFlags, /*edgeFlags=*/0, AMETA_NONE, mButtonState,
                          MotionClassification::NONE, mTransform,
-                         /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
-                         mRawYCursorPosition, mRawTransform, mDownTime, mEventTime,
-                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
+                         /*xPrecision=*/0, /*yPrecision=*/0, xCursorPosition, yCursorPosition,
+                         mRawTransform, mDownTime, mEventTime, mPointers.size(),
+                         pointerProperties.data(), pointerCoords.data());
         return event;
     }
 
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 6548810..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);
 };
 
 /*
@@ -363,7 +364,8 @@
      * 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,
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index 92d5ec4..67b37b1 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -126,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. */
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index f715039..200c301 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -43,7 +43,9 @@
 class JerkTracker {
 public:
     // Initialize the tracker. If normalizedDt is true, assume that each sample pushed has dt=1.
-    JerkTracker(bool normalizedDt);
+    // 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);
@@ -58,10 +60,13 @@
 
 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;
 };
 
 /**
@@ -124,15 +129,17 @@
 
     std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers;
     std::optional<MotionEvent> mLastEvent;
-    // 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.
-    JerkTracker mJerkTracker{true};
 
-    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 728a8e1..49e909e 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -110,6 +110,9 @@
         // 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/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/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 8c356d0..e5eee34 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -108,6 +108,10 @@
                                         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
 
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/binder/Android.bp b/libs/binder/Android.bp
index cdc7166..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",
@@ -450,8 +455,79 @@
     ],
 }
 
+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",
+    bool_variables: ["release_libbinder_client_cache"],
+    properties: [
+        "cflags",
+    ],
+}
+
+libbinder_client_cache_config {
+    name: "libbinder_client_cache_flag",
+    soong_config_variables: {
+        release_libbinder_client_cache: {
+            cflags: ["-DLIBBINDER_CLIENT_CACHE"],
+            conditions_default: {
+                cflags: ["-DNO_LIBBINDER_CLIENT_CACHE"],
+            },
+        },
+    },
+}
+
+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",
+        "libbinder_addservice_cache_flag",
+        "libbinder_remove_cache_static_list_flag",
+    ],
     srcs: [
         "BufferedTextOutput.cpp",
         "BackendUnifiedServiceManager.cpp",
@@ -461,6 +537,7 @@
         "ProcessState.cpp",
         "Static.cpp",
         ":libbinder_aidl",
+        ":libbinder_accessor_aidl",
         ":libbinder_device_interface_sources",
     ],
     target: {
@@ -771,11 +848,22 @@
         "aidl/android/os/IClientCallback.aidl",
         "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",
     ],
     path: "aidl",
 }
 
+filegroup {
+    name: "libbinder_accessor_aidl",
+    srcs: [
+        "aidl/android/os/IAccessor.aidl",
+    ],
+    path: "aidl",
+    visibility: [":__subpackages__"],
+}
+
 aidl_interface {
     name: "packagemanager_aidl",
     unstable: true,
@@ -790,6 +878,7 @@
     backend: {
         rust: {
             apex_available: [
+                "//apex_available:platform",
                 "com.android.virt",
             ],
             enabled: true,
@@ -832,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 b0d3048..7c0319a 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -15,86 +15,478 @@
  */
 #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__)
 #include <android-base/properties.h>
 #endif
 
 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 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) {}
+      : mTheRealServiceManager(impl) {
+    mCacheForGetService = std::make_shared<BinderCacheWithInvalidation>();
+}
 
-sp<AidlServiceManager> BackendUnifiedServiceManager::getImpl() {
-    return mTheRealServiceManager;
+Status BackendUnifiedServiceManager::getService(const ::std::string& name,
+                                                sp<IBinder>* _aidl_return) {
+    os::Service service;
+    Status status = getService2(name, &service);
+    if (status.isOk()) {
+        *_aidl_return = service.get<os::Service::Tag::serviceWithMetadata>().service;
+    }
+    return status;
 }
-binder::Status BackendUnifiedServiceManager::getService(const ::std::string& name,
-                                                        sp<IBinder>* _aidl_return) {
-    return mTheRealServiceManager->getService(name, _aidl_return);
+
+Status BackendUnifiedServiceManager::getService2(const ::std::string& name, os::Service* _out) {
+    if (returnIfCached(name, _out)) {
+        return Status::ok();
+    }
+    os::Service service;
+    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,
-                                                          sp<IBinder>* _aidl_return) {
-    return mTheRealServiceManager->checkService(name, _aidl_return);
+
+Status BackendUnifiedServiceManager::checkService(const ::std::string& name,
+                                                  sp<IBinder>* _aidl_return) {
+    os::Service service;
+    Status status = checkService2(name, &service);
+    if (status.isOk()) {
+        *_aidl_return = service.get<os::Service::Tag::serviceWithMetadata>().service;
+    }
+    return status;
 }
-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::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;
 }
-binder::Status BackendUnifiedServiceManager::listServices(
-        int32_t dumpPriority, ::std::vector<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->listServices(dumpPriority, _aidl_return);
+
+Status BackendUnifiedServiceManager::toBinderService(const ::std::string& name,
+                                                     const os::Service& in, os::Service* _out) {
+    switch (in.getTag()) {
+        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;
+            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::serviceWithMetadata>(
+                        createServiceWithMetadata(nullptr, false));
+                return Status::ok();
+            }
+            auto request = [=] {
+                os::ParcelFileDescriptor fd;
+                Status ret = accessor->addConnection(&fd);
+                if (ret.isOk()) {
+                    return base::unique_fd(fd.release());
+                } else {
+                    ALOGE("Failed to connect to RpcSession: %s", ret.toString8().c_str());
+                    return base::unique_fd(-1);
+                }
+            };
+            auto session = RpcSession::make();
+            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::serviceWithMetadata>(
+                    createServiceWithMetadata(session->getRootObject(), false));
+            return Status::ok();
+        }
+        default: {
+            LOG_ALWAYS_FATAL("Unknown service type: %d", in.getTag());
+        }
+    }
 }
-binder::Status BackendUnifiedServiceManager::registerForNotifications(
+
+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);
+}
+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;
+}
+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)) {
@@ -104,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) {
@@ -120,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 d72b5bb..c14f280 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -18,16 +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, 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,
@@ -51,17 +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;
+    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..0a22588 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -143,6 +143,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 +288,7 @@
     // for below objects
     RpcMutex mLock;
     std::set<sp<RpcServerLink>> mRpcServerLinks;
-    BpBinder::ObjectManager mObjects;
+    BpBinder::ObjectManager mObjectMgr;
 
     unique_fd mRecordingFd;
 };
@@ -452,7 +468,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 +477,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 +485,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 +501,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..444f061 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -78,7 +78,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 +153,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 +179,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 +192,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"
@@ -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/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/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..623e7b9 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -89,26 +89,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 +210,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 +232,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 +298,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 +775,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 +994,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),
@@ -1177,13 +1245,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 +1264,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 +1551,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;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 17e522d..719e445 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,13 +26,19 @@
 #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
@@ -57,6 +65,8 @@
 
 using AidlServiceManager = android::os::IServiceManager;
 using android::binder::Status;
+using android::os::IAccessor;
+using android::os::Service;
 
 // libbinder's IServiceManager.h can't rely on the values generated by AIDL
 // because many places use its headers via include_dirs (meaning, without
@@ -76,10 +86,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;
@@ -118,6 +127,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
@@ -133,23 +144,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) {
-        return mUnifiedServiceManager->getService(name, _aidl_return);
+        Service service;
+        Status status = mUnifiedServiceManager->getService2(name, &service);
+        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;
@@ -167,6 +344,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
 
@@ -247,8 +544,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)) {
@@ -268,27 +568,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
@@ -305,7 +602,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) {
@@ -325,25 +622,22 @@
     return nullptr;
 }
 
-sp<IBinder> ServiceManagerShim::checkService(const String16& name) const
-{
-    sp<IBinder> ret;
-    if (!mUnifiedServiceManager->checkService(String8(name).c_str(), &ret).isOk()) {
+sp<IBinder> CppBackendShim::checkService(const String16& name) const {
+    Service ret;
+    if (!mUnifiedServiceManager->checkService2(String8(name).c_str(), &ret).isOk()) {
         return nullptr;
     }
-    return ret;
+    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 {};
@@ -357,8 +651,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 {
@@ -391,7 +684,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? "
@@ -425,9 +719,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).
@@ -447,7 +742,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()) {
@@ -458,7 +753,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);
@@ -476,7 +771,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()) {
@@ -487,7 +782,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()) {
@@ -504,7 +799,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 =
@@ -519,8 +814,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;
@@ -539,9 +834,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();) {
@@ -560,8 +855,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;
@@ -590,7 +885,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);
@@ -608,21 +903,21 @@
 }
 
 #ifndef __ANDROID__
-// ServiceManagerShim for host. Implements the old libbinder android::IServiceManager API.
+// 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();
@@ -643,7 +938,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 e8fe555..65c6553 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -616,6 +616,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 +669,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 +685,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 +798,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 +841,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 +882,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 +975,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 +1066,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 +1320,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 +1375,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 +1724,9 @@
                 }
             }
         }
-        ::munmap(ptr, len);
+        if (::munmap(ptr, len) == -1) {
+            ALOGW("munmap() failed: %s", strerror(errno));
+        }
     }
     ::close(fd);
     return status;
@@ -2695,6 +2696,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();
@@ -3284,14 +3286,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();
@@ -3332,7 +3326,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..0e1e9b4 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>
@@ -57,6 +56,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 +329,7 @@
 sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
 {
     sp<IBinder> result;
+    std::function<void()> postTask;
 
     std::unique_lock<std::mutex> _l(mLock);
 
@@ -358,7 +377,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 +390,10 @@
         }
     }
 
+    _l.unlock();
+
+    if (postTask) postTask();
+
     return result;
 }
 
@@ -387,7 +410,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 +452,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 +492,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 +501,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) {
@@ -577,7 +616,7 @@
 #ifdef __ANDROID__
     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 d9e926a..c7851dc 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -71,8 +71,23 @@
     return setupSocketServer(UnixSocketAddress(path));
 }
 
-status_t RpcServer::setupVsockServer(unsigned int bindCid, unsigned int port) {
-    return setupSocketServer(VsockSocketAddress(bindCid, port));
+status_t RpcServer::setupVsockServer(unsigned bindCid, unsigned port, unsigned* assignedPort) {
+    auto status = setupSocketServer(VsockSocketAddress(bindCid, port));
+    if (status != OK) return status;
+
+    if (assignedPort == nullptr) return OK;
+    sockaddr_vm addr;
+    socklen_t len = sizeof(addr);
+    if (0 != getsockname(mServer.fd.get(), reinterpret_cast<sockaddr*>(&addr), &len)) {
+        status = -errno;
+        ALOGE("setupVsockServer: Failed to getsockname: %s", strerror(-status));
+        return status;
+    }
+
+    LOG_ALWAYS_FATAL_IF(len != sizeof(addr), "Wrong socket type: len %zu vs len %zu",
+                        static_cast<size_t>(len), sizeof(addr));
+    *assignedPort = addr.svm_port;
+    return OK;
 }
 
 status_t RpcServer::setupInetServer(const char* address, unsigned int port,
@@ -488,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 16a7f9f..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());
@@ -801,6 +817,14 @@
     return true;
 }
 
+void RpcSession::setSessionSpecificRoot(const sp<IBinder>& sessionSpecificRoot) {
+    LOG_ALWAYS_FATAL_IF(mSessionSpecificRootObject != nullptr,
+                        "Session specific root object already set");
+    LOG_ALWAYS_FATAL_IF(mForServer != nullptr,
+                        "Session specific root object cannot be set for a server");
+    mSessionSpecificRootObject = sessionSpecificRoot;
+}
+
 sp<RpcSession::RpcConnection> RpcSession::assignIncomingConnectionToThisThread(
         std::unique_ptr<RpcTransport> rpcTransport) {
     RpcMutexLockGuard _l(mMutex);
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/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/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
new file mode 100644
index 0000000..c06e05c
--- /dev/null
+++ b/libs/binder/aidl/android/os/IAccessor.aidl
@@ -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.
+ */
+
+package android.os;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Interface for accessing the RPC server of a service.
+ *
+ * @hide
+ */
+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();
+
+    /**
+     * 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 0fb1615..6539238 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -18,6 +18,7 @@
 
 import android.os.IClientCallback;
 import android.os.IServiceCallback;
+import android.os.Service;
 import android.os.ServiceDebugInfo;
 import android.os.ConnectionInfo;
 
@@ -48,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;
 
@@ -59,17 +62,41 @@
      * exists for legacy purposes.
      *
      * Returns null if the service does not exist.
+     *
+     * @deprecated TODO(b/355394904): Use getService2 instead. This does not return metadata
+     * that is included in ServiceWithMetadata
      */
     @UnsupportedAppUsage
     @nullable IBinder getService(@utf8InCpp String name);
 
     /**
+     * Retrieve an existing service called @a name from the
+     * service manager.
+     *
+     * This is the same as checkService (returns immediately) but
+     * exists for legacy purposes.
+     *
+     * Returns an enum Service that can be of different types. The
+     * enum value is null if the service does not exist.
+     */
+    Service getService2(@utf8InCpp String name);
+
+    /**
+     * 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
-    @nullable IBinder 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
new file mode 100644
index 0000000..3bc6588
--- /dev/null
+++ b/libs/binder/aidl/android/os/Service.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import android.os.ServiceWithMetadata;
+
+/**
+ * Service is a union of different service types that can be returned
+ * by the internal {@link ServiceManager#getService(name)} API.
+ *
+ * @hide
+ */
+union Service {
+    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/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index abea0fb..c241d31 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -85,9 +85,12 @@
 
     /**
      * Creates an RPC server binding to the given CID at the given port.
+     *
+     * Set |port| to VMADDR_PORT_ANY to pick an ephemeral port. In this case, |assignedPort|
+     * will be set to the picked port number, if it is not null.
      */
-    [[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockServer(unsigned int bindCid,
-                                                               unsigned int port);
+    [[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockServer(unsigned bindCid, unsigned port,
+                                                               unsigned* assignedPort = nullptr);
 
     /**
      * Creates an RPC server at the current port using IPv4.
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index 40102bb..af37bf2 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -220,6 +220,12 @@
     // internal only
     LIBBINDER_EXPORTED const std::unique_ptr<RpcState>& state() { return mRpcBinderState; }
 
+    /**
+     * Sets the session-specific root object. This is the object that will be used to attach
+     * the IAccessor binder to the RpcSession when a binder is set up via accessor.
+     */
+    LIBBINDER_EXPORTED void setSessionSpecificRoot(const sp<IBinder>& sessionSpecificRoot);
+
 private:
     friend sp<RpcSession>;
     friend RpcServer;
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 af280d3..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;
@@ -385,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,
@@ -404,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");
 
@@ -734,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;
@@ -872,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 f5b738c..a93dc1f 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -132,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;
@@ -151,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):
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 c1d0e9f..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,17 +22,14 @@
 #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 {
 
@@ -44,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();
         }
     }
@@ -54,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;
@@ -70,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;
@@ -81,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;
@@ -96,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;
@@ -109,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;
@@ -148,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));
@@ -159,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;
@@ -167,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;
@@ -175,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) {
@@ -222,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);
@@ -230,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);
@@ -238,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);
@@ -246,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*));
@@ -261,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;
@@ -275,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;
@@ -283,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;
@@ -291,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;
@@ -303,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);
@@ -321,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);
@@ -343,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);
         }
@@ -389,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) {
@@ -406,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) {
@@ -438,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_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 41b30a0..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
@@ -35,6 +34,7 @@
     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
 };
 
 /**
@@ -257,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..6aff994 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.
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/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 826e199..a637165 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 {
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/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index f518a22..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.";
 }
 
@@ -1102,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);
 
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 2deb254..8404a48 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -15,6 +15,7 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
     ],
     host_supported: true,
     vendor_available: true,
@@ -79,6 +80,9 @@
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -129,9 +133,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 +182,9 @@
         // rustified
         "libbinder_ndk_bindgen_flags.txt",
     ],
+    cflags: [
+        "-DANDROID_PLATFORM",
+    ],
     shared_libs: [
         "libbinder_ndk_on_trusty_mock",
         "libc++",
@@ -185,6 +204,7 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
     ],
 }
 
@@ -196,4 +216,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..4036551 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -27,11 +27,11 @@
     visibility: [
         "//device/google/cuttlefish/shared/minidroid/sample",
         "//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 +60,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.compos",
+        "com.android.microfuchsia",
         "com.android.uwb",
         "com.android.virt",
     ],
@@ -93,6 +94,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..8c0501b 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))
             }
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index e70f4f0..77b80fe 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;
+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..d71ed73
--- /dev/null
+++ b/libs/binder/rust/src/persistable_bundle.rs
@@ -0,0 +1,691 @@
+/*
+ * 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_getBooleanVector,
+    APersistableBundle_getDouble, APersistableBundle_getDoubleVector, APersistableBundle_getInt,
+    APersistableBundle_getIntVector, APersistableBundle_getLong, APersistableBundle_getLongVector,
+    APersistableBundle_getPersistableBundle, 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_KEY_NOT_FOUND,
+};
+use std::ffi::{c_char, CString, NulError};
+use std::ptr::{null_mut, NonNull};
+
+/// 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 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 + Default>(
+        &self,
+        key: &str,
+        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),
+            required_buffer_size => {
+                let mut value = vec![
+                    T::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");
+                    }
+                    _ => 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, 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, 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, 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, APersistableBundle_getDoubleVector) }
+    }
+
+    /// 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)
+        }
+    }
+}
+
+// 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",
+        )))
+    }
+}
+
+impl_deserialize_for_unstructured_parcelable!(PersistableBundle);
+impl_serialize_for_unstructured_parcelable!(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));
+    }
+
+    #[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_string() {
+        let mut bundle = PersistableBundle::new();
+        assert_eq!(bundle.insert_string("string", "foo"), Ok(()));
+        assert_eq!(bundle.size(), 1);
+    }
+
+    #[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])));
+    }
+
+    #[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)));
+    }
+}
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/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 18b178b..78fe2a8 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -123,11 +123,19 @@
         // We can't send BpBinder for regular binder over RPC.
         return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
     }
+    android::binder::Status getService2(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);
+    }
     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);
+    }
     android::binder::Status addService(const std::string&, const android::sp<android::IBinder>&,
                                        bool, int32_t) override {
         // We can't send BpBinder for RPC over regular binder.
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 4c7684c..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,
@@ -235,6 +267,16 @@
         "binder_test_defaults",
     ],
 
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
     static_libs: [
         "libbinder_test_utils",
         "libbinder_tls_static",
@@ -267,7 +309,6 @@
     defaults: [
         "binderRpcTest_common_defaults",
     ],
-    compile_multilib: "first",
 
     srcs: [
         "binderRpcTest.cpp",
@@ -495,6 +536,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_test {
@@ -691,8 +735,11 @@
         "liblog",
         "libutils",
     ],
+    static_libs: [
+        "libgmock",
+    ],
     test_suites: [
-        "device-tests",
+        "general-tests",
         "vts",
     ],
     require_root: true,
@@ -749,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,
@@ -875,6 +944,7 @@
             enabled: false,
         },
     },
+    corpus: ["corpus/*"],
     fuzz_config: {
         cc: [
             "smoreland@google.com",
diff --git a/libs/binder/tests/BinderRpcTestServerConfig.aidl b/libs/binder/tests/BinderRpcTestServerConfig.aidl
index b2e0ef2..96550bc 100644
--- a/libs/binder/tests/BinderRpcTestServerConfig.aidl
+++ b/libs/binder/tests/BinderRpcTestServerConfig.aidl
@@ -20,7 +20,6 @@
     int socketType;
     int rpcSecurity;
     int serverVersion;
-    int vsockPort;
     int socketFd; // Inherited from the parent process.
     @utf8InCpp String addr;
 }
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 cec0dbf..9f656ec 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -19,6 +19,12 @@
 #include <aidl/IBinderRpcTest.h>
 #endif
 
+#if defined(__LP64__)
+#define TEST_FILE_SUFFIX "64"
+#else
+#define TEST_FILE_SUFFIX "32"
+#endif
+
 #include <chrono>
 #include <cstdlib>
 #include <iostream>
@@ -40,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;
@@ -62,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));
@@ -141,11 +156,6 @@
     return ret;
 };
 
-static unsigned int allocateVsockPort() {
-    static unsigned int vsockPort = 34567;
-    return vsockPort++;
-}
-
 static unique_fd initUnixSocket(std::string addr) {
     auto socket_addr = UnixSocketAddress(addr.c_str());
     unique_fd fd(TEMP_FAILURE_RETRY(socket(socket_addr.addr()->sa_family, SOCK_STREAM, AF_UNIX)));
@@ -264,7 +274,8 @@
 
     std::string path = GetExecutableDirectory();
     auto servicePath = path + "/binder_rpc_test_service" +
-            (singleThreaded ? "_single_threaded" : "") + (noKernel ? "_no_kernel" : "");
+            (singleThreaded ? "_single_threaded" : "") + (noKernel ? "_no_kernel" : "") +
+            TEST_FILE_SUFFIX;
 
     unique_fd bootstrapClientFd, socketFd;
 
@@ -300,7 +311,6 @@
     serverConfig.socketType = static_cast<int32_t>(socketType);
     serverConfig.rpcSecurity = static_cast<int32_t>(rpcSecurity);
     serverConfig.serverVersion = serverVersion;
-    serverConfig.vsockPort = allocateVsockPort();
     serverConfig.addr = addr;
     serverConfig.socketFd = socketFd.get();
     for (auto mode : options.serverSupportedFileDescriptorTransportModes) {
@@ -364,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:
-                status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
-                break;
-            case SocketType::INET:
-                status = session->setupInetClient("127.0.0.1", serverInfo.port);
-                break;
+            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: {
+                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
@@ -412,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;
 }
@@ -422,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});
 
@@ -467,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) {
@@ -483,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) {
@@ -498,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) {
@@ -1126,6 +1165,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() {
@@ -1152,8 +1797,6 @@
 #else // BINDER_RPC_TO_TRUSTY_TEST
 bool testSupportVsockLoopback() {
     // We don't need to enable TLS to know if vsock is supported.
-    unsigned int vsockPort = allocateVsockPort();
-
     unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
 
@@ -1165,12 +1808,12 @@
 
     sockaddr_vm serverAddr{
             .svm_family = AF_VSOCK,
-            .svm_port = vsockPort,
+            .svm_port = VMADDR_PORT_ANY,
             .svm_cid = VMADDR_CID_ANY,
     };
     int ret = TEMP_FAILURE_RETRY(
             bind(serverFd.get(), reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)));
-    LOG_ALWAYS_FATAL_IF(0 != ret, "Could not bind socket to port %u: %s", vsockPort,
+    LOG_ALWAYS_FATAL_IF(0 != ret, "Could not bind socket to port VMADDR_PORT_ANY: %s",
                         strerror(errno));
 
     socklen_t len = sizeof(serverAddr);
@@ -1180,7 +1823,7 @@
                         "getsockname didn't read the full addr struct");
 
     ret = TEMP_FAILURE_RETRY(listen(serverFd.get(), 1 /*backlog*/));
-    LOG_ALWAYS_FATAL_IF(0 != ret, "Could not listen socket on port %u: %s", vsockPort,
+    LOG_ALWAYS_FATAL_IF(0 != ret, "Could not listen socket on port %u: %s", serverAddr.svm_port,
                         strerror(errno));
 
     // Try to connect to the server using the VMADDR_CID_LOCAL cid
@@ -1189,13 +1832,13 @@
     // and they return ETIMEDOUT after that.
     unique_fd connectFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
-    LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s", vsockPort,
-                        strerror(errno));
+    LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s",
+                        serverAddr.svm_port, strerror(errno));
 
     bool success = false;
     sockaddr_vm connectAddr{
             .svm_family = AF_VSOCK,
-            .svm_port = vsockPort,
+            .svm_port = serverAddr.svm_port,
             .svm_cid = VMADDR_CID_LOCAL,
     };
     ret = TEMP_FAILURE_RETRY(connect(connectFd.get(), reinterpret_cast<sockaddr*>(&connectAddr),
@@ -1316,6 +1959,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>> {};
 
@@ -1386,7 +2034,7 @@
     ASSERT_NE(nullptr, sm);
     // Any Java service with non-empty getInterfaceDescriptor() would do.
     // Let's pick activity.
-    auto binder = sm->checkService(String16("activity"));
+    auto binder = sm->checkService(String16(kKnownAidlService));
     ASSERT_NE(nullptr, binder);
     auto descriptor = binder->getInterfaceDescriptor();
     ASSERT_GE(descriptor.size(), 0u);
@@ -1544,8 +2192,9 @@
                     };
                 } break;
                 case SocketType::VSOCK: {
-                    auto port = allocateVsockPort();
-                    auto status = rpcServer->setupVsockServer(VMADDR_CID_LOCAL, port);
+                    unsigned port;
+                    auto status =
+                            rpcServer->setupVsockServer(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, &port);
                     if (status != OK) {
                         return AssertionFailure() << "setupVsockServer: " << statusToString(status);
                     }
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 28125f1..aef9464 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -143,8 +143,8 @@
             break;
         case SocketType::VSOCK:
             LOG_ALWAYS_FATAL_IF(OK !=
-                                        server->setupVsockServer(VMADDR_CID_LOCAL,
-                                                                 serverConfig.vsockPort),
+                                        server->setupVsockServer(VMADDR_CID_LOCAL, VMADDR_PORT_ANY,
+                                                                 &outPort),
                                 "Need `sudo modprobe vsock_loopback`?");
             break;
         case SocketType::INET: {
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index f480780..4b9dcf8 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -301,7 +301,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 +324,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 +498,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..a3fc9cc
--- /dev/null
+++ b/libs/binder/tests/binderStabilityIntegrationTest.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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::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 e378b86..20b6b44 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -318,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/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/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/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/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/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
index ff3ae5a..4f293b9 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
@@ -1,7 +1,5 @@
 package com.android.graphics.bufferstreamsdemoapp
 
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
@@ -29,10 +27,11 @@
             navigationIcon = {
                 if (canNavigateBack) {
                     IconButton(onClick = navigateUp) {
-                        Icon(
-                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,
-                                contentDescription = stringResource(R.string.back_button)
-                        )
+                        // b/355293776
+                        // Icon(
+                        //     imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+                        //     contentDescription = stringResource(R.string.back_button)
+                        // )
                     }
                 }
             }
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/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 81f6a58..44cdc02 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -41,7 +41,7 @@
 static constexpr uint64_t NSEC_PER_YEAR = NSEC_PER_SEC * 60 * 60 * 24 * 365;
 
 // Declare busy loop variable globally to prevent removal during optimization
-static long sum __attribute__((used)) = 0;
+static volatile long sum __attribute__((used)) = 1;
 
 using std::vector;
 
@@ -579,8 +579,8 @@
 
 // Keeps CPU busy with some number crunching
 void useCpu() {
-    sum = 0;
-    for (int i = 0; i < 100000; i++) {
+    sum = 1;
+    for (int i = 1; i < 100000; i++) {
         sum *= i;
     }
 }
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/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/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 52383ac..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 ||
@@ -508,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 =
@@ -552,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
@@ -562,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;
     }
@@ -631,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/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 23f583b..72f29c6 100644
--- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
+++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
@@ -125,6 +125,11 @@
         VULKAN_DEVICE_EXTENSION = 9,
     };
 
+    enum GLTelemetryHints {
+        NO_HINT = 0,
+        SKIP_TELEMETRY = 1,
+    };
+
     GpuStatsInfo() = default;
     GpuStatsInfo(const GpuStatsInfo&) = default;
     virtual ~GpuStatsInfo() = default;
@@ -136,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 b0ab0b9..452e48b 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -114,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/gui/Android.bp b/libs/gui/Android.bp
index 51d2e53..1243b21 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -255,6 +255,7 @@
         "BitTube.cpp",
         "BLASTBufferQueue.cpp",
         "BufferItemConsumer.cpp",
+        "BufferReleaseChannel.cpp",
         "Choreographer.cpp",
         "CompositorTiming.cpp",
         "ConsumerBase.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 044170c..25e6a52 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);
@@ -210,6 +224,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 +256,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 +287,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 +327,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 +365,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 +407,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 +423,7 @@
                                                 stat.currentMaxAcquiredBufferCount,
                                                 true /* fakeRelease */);
                 }
+#endif
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
             }
@@ -403,23 +431,6 @@
             BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was "
                      "empty.");
         }
-
-        decStrong((void*)transactionCallbackThunk);
-    }
-}
-
-// Unlike transactionCallbackThunk the release buffer callback does not extend the life of the
-// 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());
     }
 }
 
@@ -432,6 +443,23 @@
     }
 }
 
+// Unlike transactionCallbackThunk the release buffer callback does not extend the life of the
+// 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.
+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(
         const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
         std::optional<uint32_t> currentMaxAcquiredBufferCount) {
@@ -594,9 +622,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,9 +634,12 @@
                            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;
@@ -629,7 +657,7 @@
     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);
 
@@ -786,9 +814,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 +845,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,
@@ -1113,9 +1141,9 @@
 // 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 {
@@ -1130,27 +1158,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);
     }
@@ -1230,7 +1263,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 a4d105d..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, ...)                                                                           \
@@ -924,6 +927,7 @@
     uint64_t currentFrameNumber = 0;
     BufferItem item;
     int connectedApi;
+    bool enableEglCpuThrottling = true;
     sp<Fence> lastQueuedFence;
 
     { // Autolock scope
@@ -1097,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);
@@ -1142,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.
@@ -1501,7 +1508,6 @@
 
     const bool useDefaultSize = !width && !height;
     while (true) {
-        size_t newBufferCount = 0;
         uint32_t allocWidth = 0;
         uint32_t allocHeight = 0;
         PixelFormat allocFormat = PIXEL_FORMAT_UNKNOWN;
@@ -1523,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;
             }
 
@@ -1566,27 +1573,23 @@
         };
 #endif
 
-        Vector<sp<GraphicBuffer>> buffers;
-        for (size_t i = 0; i < newBufferCount; ++i) {
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
-            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
 #else
-            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
-                    allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
-                    allocUsage, allocName);
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
+                allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
+                allocUsage, allocName);
 #endif
 
-            status_t result = graphicBuffer->initCheck();
+        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;
-            }
-            buffers.push_back(graphicBuffer);
+        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
@@ -1614,15 +1617,13 @@
                 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;
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/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/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/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/LayerState.cpp b/libs/gui/LayerState.cpp
index 3745805..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>
@@ -177,6 +176,7 @@
     }
 
     SAFE_PARCEL(output.write, stretchEffect);
+    SAFE_PARCEL(output.writeParcelable, edgeExtensionParameters);
     SAFE_PARCEL(output.write, bufferCrop);
     SAFE_PARCEL(output.write, destinationFrame);
     SAFE_PARCEL(output.writeInt32, static_cast<uint32_t>(trustedOverlay));
@@ -193,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;
 }
 
@@ -306,6 +313,7 @@
     }
 
     SAFE_PARCEL(input.read, stretchEffect);
+    SAFE_PARCEL(input.readParcelable, &edgeExtensionParameters);
     SAFE_PARCEL(input.read, bufferCrop);
     SAFE_PARCEL(input.read, destinationFrame);
     uint32_t trustedOverlayInt;
@@ -337,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;
 }
 
@@ -682,6 +697,10 @@
         what |= eStretchChanged;
         stretchEffect = other.stretchEffect;
     }
+    if (other.what & eEdgeExtensionChanged) {
+        what |= eEdgeExtensionChanged;
+        edgeExtensionParameters = other.edgeExtensionParameters;
+    }
     if (other.what & eBufferCropChanged) {
         what |= eBufferCropChanged;
         bufferCrop = other.bufferCrop;
@@ -712,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,
@@ -783,6 +806,7 @@
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
     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;
@@ -790,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;
 }
 
@@ -867,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) {
diff --git a/libs/gui/OWNERS b/libs/gui/OWNERS
index 070f6bf..b97cee3 100644
--- a/libs/gui/OWNERS
+++ b/libs/gui/OWNERS
@@ -1,6 +1,8 @@
 # Bug component: 1075131
 
+carlosmr@google.com
 chrisforbes@google.com
+jshargo@google.com
 jreck@google.com
 
 file:/services/surfaceflinger/OWNERS
@@ -9,10 +11,10 @@
 
 # BufferQueue is feature-frozen
 per-file BufferQueue* = set noparent
-per-file BufferQueue* = jreck@google.com, sumir@google.com, alecmouri@google.com
+per-file BufferQueue* = jreck@google.com, sumir@google.com, alecmouri@google.com, jshargo@google.com, carlosmr@google.com
 per-file IGraphicBuffer* = set noparent
-per-file IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com
+per-file IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com, jshargo@google.com, carlosmr@google.com
 per-file include/gui/BufferQueue* = set noparent
-per-file include/gui/BufferQueue* = jreck@google.com, sumir@google.com, alecmouri@google.com
+per-file include/gui/BufferQueue* = jreck@google.com, sumir@google.com, alecmouri@google.com, jshargo@google.com, carlosmr@google.com
 per-file include/gui/IGraphicBuffer* = set noparent
-per-file include/gui/IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com
+per-file include/gui/IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com, jshargo@google.com, carlosmr@google.com
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 87fd448..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.
 
@@ -1860,30 +2072,23 @@
 }
 
 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;
@@ -1898,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;
@@ -1911,7 +2123,6 @@
     return err;
 }
 
-
 int Surface::disconnect(int api, IGraphicBufferProducer::DisconnectMode mode) {
     ATRACE_CALL();
     ALOGV("Surface::disconnect");
@@ -1939,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;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index af91bb3..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,7 +89,8 @@
     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{};
@@ -207,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
@@ -256,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);
@@ -272,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);
@@ -325,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);
 
@@ -366,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);
@@ -486,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);
-            }
         }
     }
 }
@@ -713,7 +827,6 @@
 
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
       : mId(other.mId),
-        mTransactionNestCount(other.mTransactionNestCount),
         mAnimation(other.mAnimation),
         mEarlyWakeupStart(other.mEarlyWakeupStart),
         mEarlyWakeupEnd(other.mEarlyWakeupEnd),
@@ -753,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();
@@ -850,7 +962,6 @@
 
     // Parsing was successful. Update the object.
     mId = transactionId;
-    mTransactionNestCount = transactionNestCount;
     mAnimation = animation;
     mEarlyWakeupStart = earlyWakeupStart;
     mEarlyWakeupEnd = earlyWakeupEnd;
@@ -882,7 +993,6 @@
     const_cast<SurfaceComposerClient::Transaction*>(this)->cacheBuffers();
 
     parcel->writeUint64(mId);
-    parcel->writeUint32(mTransactionNestCount);
     parcel->writeBool(mAnimation);
     parcel->writeBool(mEarlyWakeupStart);
     parcel->writeBool(mEarlyWakeupEnd);
@@ -1004,8 +1114,9 @@
 
         // register all surface controls for all callbackIds for this listener that is merging
         for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
-            mTransactionCompletedListener->addSurfaceControlToCallbacks(currentProcessCallbackInfo,
-                                                                        surfaceControl);
+            mTransactionCompletedListener
+                    ->addSurfaceControlToCallbacks(surfaceControl,
+                                                   currentProcessCallbackInfo.callbackIds);
         }
     }
 
@@ -1033,7 +1144,6 @@
     mInputWindowCommands.clear();
     mUncacheBuffers.clear();
     mMayContainBuffer = false;
-    mTransactionNestCount = 0;
     mAnimation = false;
     mEarlyWakeupStart = false;
     mEarlyWakeupEnd = false;
@@ -1059,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(), {});
@@ -1361,7 +1472,7 @@
     auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
     callbackInfo.surfaceControls.insert(sc);
 
-    mTransactionCompletedListener->addSurfaceControlToCallbacks(callbackInfo, sc);
+    mTransactionCompletedListener->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -1940,8 +2051,9 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::addTransactionCallback(
         TransactionCompletedCallbackTakesContext callback, void* callbackContext,
         CallbackId::Type callbackType) {
-    auto callbackWithContext = std::bind(callback, callbackContext, std::placeholders::_1,
-                                         std::placeholders::_2, std::placeholders::_3);
+    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 =
@@ -1955,13 +2067,15 @@
 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(
@@ -2217,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);
@@ -2262,6 +2393,22 @@
     return *this;
 }
 
+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::eBufferReleaseChannelChanged;
+    s->bufferReleaseChannel = channel;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 // ---------------------------------------------------------------------------
 
 DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
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/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
index 8ed08c2..fd035f6 100644
--- a/libs/gui/aidl/Android.bp
+++ b/libs/gui/aidl/Android.bp
@@ -28,9 +28,6 @@
         ":libgui_extra_unstructured_aidl_files",
 
         "android/gui/BitTube.aidl",
-        "android/gui/CaptureArgs.aidl",
-        "android/gui/DisplayCaptureArgs.aidl",
-        "android/gui/LayerCaptureArgs.aidl",
         "android/gui/LayerMetadata.aidl",
         "android/gui/ParcelableVsyncEventData.aidl",
         "android/gui/ScreenCaptureResults.aidl",
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 9f198ca..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" rust_type "gui_aidl_types_rs::CaptureArgs";
+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 fc97dbf..e00a2df 100644
--- a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
@@ -16,5 +16,18 @@
 
 package android.gui;
 
-parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::DisplayCaptureArgs";
+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/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 6d018ea..ac14138 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -42,6 +42,7 @@
 import android.gui.ITunnelModeEnabledListener;
 import android.gui.IWindowInfosListener;
 import android.gui.IWindowInfosPublisher;
+import android.gui.IJankListener;
 import android.gui.LayerCaptureArgs;
 import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
@@ -580,4 +581,22 @@
      * 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 18d293f..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" rust_type "gui_aidl_types_rs::LayerCaptureArgs";
+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/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
index 97a9035..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" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
\ 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/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/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/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/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 84a1730..197e792 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -867,6 +867,6 @@
 #endif
 
 // ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_GUI_IGRAPHICBUFFERPRODUCER_H
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index eb4a802..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,13 +72,6 @@
 using gui::IScreenCaptureListener;
 using gui::SpHash;
 
-namespace gui {
-
-struct DisplayCaptureArgs;
-struct LayerCaptureArgs;
-
-} // namespace gui
-
 namespace ui {
 
 struct DisplayMode;
@@ -112,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,
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/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/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 5f2f8dc..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,13 +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>
@@ -218,6 +220,8 @@
         eTrustedOverlayChanged = 0x4000'00000000,
         eDropInputModeChanged = 0x8000'00000000,
         eExtendedRangeBrightnessChanged = 0x10000'00000000,
+        eEdgeExtensionChanged = 0x20000'00000000,
+        eBufferReleaseChannelChanged = 0x40000'00000000,
     };
 
     layer_state_t();
@@ -241,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 |
@@ -393,6 +397,9 @@
     // 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;
 
@@ -407,6 +414,8 @@
 
     TrustedPresentationThresholds trustedPresentationThresholds;
     TrustedPresentationListener trustedPresentationListener;
+
+    std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel;
 };
 
 class ComposerState {
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 bdcaaf2..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);
 
@@ -321,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);
 
@@ -330,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:
@@ -357,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
@@ -387,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 {
@@ -401,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 };
@@ -422,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();
@@ -459,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 0862e03..4f9af16 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -35,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>
@@ -337,6 +340,8 @@
     static std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>
     getDisplayDecorationSupport(const sp<IBinder>& displayToken);
 
+    static bool flagEdgeExtensionEffectUseShader();
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
@@ -447,7 +452,6 @@
 
         uint64_t mId;
 
-        uint32_t mTransactionNestCount = 0;
         bool mAnimation = false;
         bool mEarlyWakeupStart = false;
         bool mEarlyWakeupEnd = false;
@@ -743,11 +747,26 @@
         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& setBufferReleaseChannel(
+                const sp<SurfaceControl>& sc,
+                const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel);
+
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
 
@@ -864,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();
@@ -904,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);
 
@@ -927,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);
@@ -943,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/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 87cef08..d3f2899 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -43,3 +43,75 @@
     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/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs
index fead018..2351df0 100644
--- a/libs/gui/rust/aidl_types/src/lib.rs
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -42,10 +42,7 @@
 }
 
 stub_unstructured_parcelable!(BitTube);
-stub_unstructured_parcelable!(CaptureArgs);
-stub_unstructured_parcelable!(DisplayCaptureArgs);
 stub_unstructured_parcelable!(DisplayInfo);
-stub_unstructured_parcelable!(LayerCaptureArgs);
 stub_unstructured_parcelable!(LayerDebugInfo);
 stub_unstructured_parcelable!(LayerMetadata);
 stub_unstructured_parcelable!(ParcelableVsyncEventData);
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index ea8acbb..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"],
@@ -25,34 +53,41 @@
         "-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",
@@ -63,10 +98,17 @@
         "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: [
         "libgmock",
     ],
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 946ff05..53f4a36 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -20,7 +20,7 @@
 
 #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>
@@ -186,6 +186,10 @@
         mBlastBufferQueueAdapter->mergeWithNextTransaction(merge, frameNumber);
     }
 
+    void setApplyToken(sp<IBinder> applyToken) {
+        mBlastBufferQueueAdapter->setApplyToken(std::move(applyToken));
+    }
+
 private:
     sp<TestBLASTBufferQueue> mBlastBufferQueueAdapter;
 };
@@ -229,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();
     }
 
@@ -510,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;
@@ -1269,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();
@@ -1327,7 +1409,7 @@
     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);
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 590e2c8..2e6ffcb 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -1430,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);
@@ -1493,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);
 
@@ -1531,14 +1523,10 @@
 }
 
 TEST_F(BufferQueueTest, TestAdditionalOptions) {
-    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);
 
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
index 2ac2550..8db48d2 100644
--- a/libs/gui/tests/Choreographer_test.cpp
+++ b/libs/gui/tests/Choreographer_test.cpp
@@ -52,25 +52,23 @@
     sp<Looper> looper = Looper::prepare(0);
     Choreographer* choreographer = Choreographer::getForThread();
     VsyncCallback animationCb;
-    VsyncCallback inputCb;
-
     choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &animationCb, 0,
                                             CALLBACK_ANIMATION);
+    VsyncCallback inputCb;
     choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &inputCb, 0,
                                             CALLBACK_INPUT);
-
-    nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    nsecs_t currTime;
-    int pollResult;
+    auto startTime = std::chrono::system_clock::now();
     do {
-        pollResult = looper->pollOnce(16);
-        currTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    } while (!(inputCb.callbackReceived() && animationCb.callbackReceived()) &&
-             (pollResult != Looper::POLL_TIMEOUT && pollResult != Looper::POLL_ERROR) &&
-             (currTime - startTime < 3000));
-
-    ASSERT_TRUE(inputCb.callbackReceived()) << "did not receive input callback";
-    ASSERT_TRUE(animationCb.callbackReceived()) << "did not receive animation callback";
+        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. "
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/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 43cd0f8..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));
@@ -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*/,
@@ -987,6 +1002,19 @@
 
     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; }
 
@@ -2134,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));
@@ -2186,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));
@@ -2213,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/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 d782f42..35d704a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
         "android/os/InputConfig.aidl",
+        "android/os/MotionEventFlag.aidl",
         "android/os/PointerIconType.aidl",
     ],
 }
@@ -231,6 +232,7 @@
         "MotionPredictorMetricsManager.cpp",
         "PrintTools.cpp",
         "PropertyMap.cpp",
+        "Resampler.cpp",
         "TfLiteMotionPredictor.cpp",
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
@@ -257,6 +259,8 @@
     ],
 
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc",
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index b098147..a2bb345 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -580,7 +580,7 @@
                               &pointerProperties[pointerCount]);
     mSampleEventTimes.clear();
     mSamplePointerCoords.clear();
-    addSample(eventTime, pointerCoords);
+    addSample(eventTime, pointerCoords, mId);
 }
 
 void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) {
@@ -640,9 +640,9 @@
     mSampleEventTimes = other.mSampleEventTimes;
 }
 
-void MotionEvent::addSample(
-        int64_t eventTime,
-        const PointerCoords* pointerCoords) {
+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()]);
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index fcf490d..1eeb4e6 100644
--- a/libs/input/InputConsumer.cpp
+++ b/libs/input/InputConsumer.cpp
@@ -135,7 +135,7 @@
     }
 
     event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
-    event.addSample(msg.body.motion.eventTime, pointerCoords);
+    event.addSample(msg.body.motion.eventTime, pointerCoords, msg.body.motion.eventId);
 }
 
 void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
@@ -235,8 +235,9 @@
             mMsgDeferred = false;
         } else {
             // Receive a fresh message.
-            status_t result = mChannel->receiveMessage(&mMsg);
-            if (result == OK) {
+            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,
@@ -244,11 +245,11 @@
 
                 // Trace the event processing timeline - event was just read from the socket
                 ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq);
-            }
-            if (result) {
+            } else {
                 // Consume the next batched event unless batches are being held for later.
-                if (consumeBatches || result != WOULD_BLOCK) {
-                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
+                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",
@@ -256,7 +257,7 @@
                         break;
                     }
                 }
-                return result;
+                return result.error().code();
             }
         }
 
@@ -696,7 +697,7 @@
                  currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
     }
 
-    event->addSample(sampleTime, touchState.lastResample.pointers);
+    event->addSample(sampleTime, touchState.lastResample.pointers, event->getId());
 }
 
 status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 15d992f..cdbc186 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "InputTransport"
+#define LOG_TAG "InputConsumerNoResampling"
 #define ATRACE_TAG ATRACE_TAG_INPUT
 
+#include <chrono>
+
 #include <inttypes.h>
 
 #include <android-base/logging.h>
@@ -31,12 +33,12 @@
 #include <input/PrintTools.h>
 #include <input/TraceTools.h>
 
-namespace input_flags = com::android::input::flags;
-
 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)
@@ -114,7 +116,7 @@
 
     // 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());
+    event.addSample(msg.body.motion.eventTime, pointerCoords.data(), msg.body.motion.eventId);
 }
 
 std::unique_ptr<TouchModeEvent> createTouchModeEvent(const InputMessage& msg) {
@@ -168,17 +170,24 @@
     return msg;
 }
 
+bool isPointerEvent(const MotionEvent& motionEvent) {
+    return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+}
 } // namespace
 
 using android::base::Result;
-using android::base::StringPrintf;
 
 // --- InputConsumerNoResampling ---
 
 InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
                                                      sp<Looper> looper,
-                                                     InputConsumerCallbacks& callbacks)
-      : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) {
+                                                     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,
@@ -190,7 +199,7 @@
 
 InputConsumerNoResampling::~InputConsumerNoResampling() {
     ensureCalledOnLooperThread(__func__);
-    consumeBatchedInputEvents(std::nullopt);
+    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,
@@ -216,8 +225,7 @@
 
     int handledEvents = 0;
     if (events & ALOOPER_EVENT_INPUT) {
-        std::vector<InputMessage> messages = readAllMessages();
-        handleMessages(std::move(messages));
+        handleMessages(readAllMessages());
         handledEvents |= ALOOPER_EVENT_INPUT;
     }
 
@@ -325,10 +333,8 @@
                 // add it to batch
                 mBatches[deviceId].emplace(msg);
             } else {
-                // consume all pending batches for this event immediately
-                // TODO(b/329776327): figure out if this could be smarter by limiting the
-                // consumption only to the current device.
-                consumeBatchedInputEvents(std::nullopt);
+                // consume all pending batches for this device immediately
+                consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt);
                 handleMessage(msg);
             }
         } else {
@@ -362,36 +368,36 @@
 std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
     std::vector<InputMessage> messages;
     while (true) {
-        InputMessage msg;
-        status_t result = mChannel->receiveMessage(&msg);
-        switch (result) {
-            case OK: {
-                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);
+        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);
-                break;
-            }
-            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;
-                break;
+            // 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;
+                }
             }
         }
     }
@@ -445,51 +451,81 @@
     }
 }
 
+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<nsecs_t> requestedFrameTime) {
+        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 frameTime.
-    const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
+    // infinite requestedFrameTime.
+    requestedFrameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
     bool producedEvents = false;
-    for (auto& [deviceId, messages] : mBatches) {
-        std::unique_ptr<MotionEvent> motion;
-        std::optional<uint32_t> firstSeqForBatch;
-        std::vector<uint32_t> sequences;
-        while (!messages.empty()) {
-            const InputMessage& msg = messages.front();
-            if (msg.body.motion.eventTime > frameTime) {
-                break;
-            }
-            if (motion == nullptr) {
-                motion = createMotionEvent(msg);
-                firstSeqForBatch = msg.header.seq;
-                const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
-                if (!inserted) {
-                    LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!";
-                }
-            } else {
-                addSample(*motion, msg);
-                mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq);
-            }
-            messages.pop();
-        }
+
+    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 frameTime is too old (all events that we have
-            // pending are in the future of the frametime). 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).
+            // 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) {
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 9333ab8..c903031 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -20,6 +20,7 @@
 #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>
@@ -31,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/",
@@ -114,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);
         }
     }
 
@@ -135,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 "";
 }
 
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 47b4228..77dcaa9 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -375,13 +375,11 @@
 
     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;
 }
 
@@ -424,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) {
@@ -435,36 +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=%s)",
-                             name.c_str(), msg->header.seq,
-                             ftl::enum_string(msg->header.type).c_str());
+                             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 {
@@ -589,7 +588,8 @@
                 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()) {
@@ -729,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",
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 1cf5612..b0563ab 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -317,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 {
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/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 5b61d39..c61d394 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -72,9 +72,13 @@
 
 // --- JerkTracker ---
 
-JerkTracker::JerkTracker(bool normalizedDt) : mNormalizedDt(normalizedDt) {}
+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();
 
@@ -115,6 +119,16 @@
         }
     }
 
+    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);
 }
@@ -125,7 +139,7 @@
 
 std::optional<float> JerkTracker::jerkMagnitude() const {
     if (mTimestamps.size() == mTimestamps.capacity()) {
-        return std::hypot(mXDerivatives[3], mYDerivatives[3]);
+        return mJerkMagnitude;
     }
     return std::nullopt;
 }
@@ -139,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
@@ -155,28 +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();
+        mJerkTracker->reset();
         mLastEvent.reset();
         return {};
     } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
@@ -211,9 +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));
+        mJerkTracker->pushSample(event.getHistoricalEventTime(i),
+                                 coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+                                 coords->getAxisValue(AMOTION_EVENT_AXIS_Y));
     }
 
     if (!mLastEvent) {
@@ -261,7 +283,7 @@
     int64_t predictionTime = mBuffers->lastTimestamp();
     const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
 
-    const float jerkMagnitude = mJerkTracker.jerkMagnitude().value_or(0);
+    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.
@@ -323,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/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 b843a4b..5250a9d 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -283,6 +283,7 @@
             .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 a77dfa5..e23fc94 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,130 +49,24 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
-     * 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.
-     */
-    const int MOTION_EVENT_FLAG_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.
-     */
-    const int MOTION_EVENT_FLAG_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
-     */
-    const int MOTION_EVENT_FLAG_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
-     */
-    const int MOTION_EVENT_FLAG_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
+     * 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;
 
     /**
-     * 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
-     */
-    const int MOTION_EVENT_FLAG_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
-     */
-    const int MOTION_EVENT_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
-     */
-    const int MOTION_EVENT_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.
+     * 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;
 
     /**
-     * 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
+     * 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;
 
-    /**
-     * 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)
-     */
-    const int MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
-
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
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/input_flags.aconfig b/libs/input/input_flags.aconfig
index a2192cb..60fb00e 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -16,13 +16,13 @@
 }
 
 flag {
-  name: "enable_gestures_library_timer_provider"
+  name: "remove_input_channel_from_windowstate"
   namespace: "input"
-  description: "Set to true to enable timer support for the touchpad Gestures library"
-  bug: "297192727"
- }
+  description: "Do not store a copy of input channel inside WindowState."
+  bug: "323450804"
+}
 
- flag {
+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"
@@ -37,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,13 +94,6 @@
 }
 
 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"
@@ -157,3 +157,53 @@
   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 564d94d..90f509d 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -19,6 +19,8 @@
 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.
@@ -193,31 +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_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32;
+        const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED.0 as u32;
         /// FLAG_HOVER_EXIT_PENDING
-        const HOVER_EXIT_PENDING = IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        const HOVER_EXIT_PENDING = MotionEventFlag::HOVER_EXIT_PENDING.0 as u32;
         /// FLAG_IS_GENERATED_GESTURE
-        const IS_GENERATED_GESTURE = IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        const IS_GENERATED_GESTURE = MotionEventFlag::IS_GENERATED_GESTURE.0 as u32;
         /// FLAG_CANCELED
-        const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32;
+        const CANCELED = MotionEventFlag::CANCELED.0 as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        const NO_FOCUS_CHANGE = MotionEventFlag::NO_FOCUS_CHANGE.0 as u32;
         /// PRIVATE_FLAG_SUPPORTS_ORIENTATION
-        const PRIVATE_SUPPORTS_ORIENTATION = IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION as u32;
+        const PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION.0 as u32;
         /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION
-        const PRIVATE_SUPPORTS_DIRECTIONAL_ORIENTATION =
-                IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION as u32;
+        const PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
         /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
         /// FLAG_TAINTED
-        const TAINTED = IInputConstants::INPUT_EVENT_FLAG_TAINTED as u32;
+        const TAINTED = MotionEventFlag::TAINTED.0 as u32;
         /// FLAG_TARGET_ACCESSIBILITY_FOCUS
-        const TARGET_ACCESSIBILITY_FOCUS = IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
+        const TARGET_ACCESSIBILITY_FOCUS = MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS.0 as u32;
     }
 }
 
@@ -320,9 +325,11 @@
 
 /// A rust enum representation of a Keyboard type.
 #[repr(u32)]
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[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,
@@ -333,10 +340,24 @@
 #[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
index 1063fac..1b89a5c 100644
--- a/libs/input/rust/keyboard_classifier.rs
+++ b/libs/input/rust/keyboard_classifier.rs
@@ -31,48 +31,46 @@
 //!    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).
-//!
-//! TODO(b/263559234): Data store implementation to store information about past classification
 
+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
-#[derive(Default)]
 pub struct KeyboardClassifier {
     device_map: HashMap<DeviceId, KeyboardInfo>,
+    data_store: DataStore,
 }
 
 struct KeyboardInfo {
-    _device: InputDevice,
+    device: InputDevice,
     keyboard_type: KeyboardType,
     is_finalized: bool,
 }
 
 impl KeyboardClassifier {
     /// Create a new KeyboardClassifier
-    pub fn new() -> Self {
-        Default::default()
+    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: device, keyboard_type, is_finalized },
-        );
+        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 {
-        return if let Some(keyboard) = self.device_map.get(&device_id) {
+        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
@@ -81,11 +79,11 @@
     /// 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 {
-        return if let Some(keyboard) = self.device_map.get(&device_id) {
+        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.
@@ -106,11 +104,16 @@
             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(&self, device: &InputDevice) -> (KeyboardType, bool) {
+    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) {
@@ -126,6 +129,21 @@
                 (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)
@@ -168,17 +186,20 @@
 
 #[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 = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
         ));
@@ -188,7 +209,7 @@
 
     #[test]
     fn classify_external_non_alphabetic_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        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);
@@ -197,7 +218,7 @@
 
     #[test]
     fn classify_mouse_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Cursor
@@ -210,7 +231,7 @@
 
     #[test]
     fn classify_touchpad_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Touchpad
@@ -223,7 +244,7 @@
 
     #[test]
     fn classify_stylus_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::ExternalStylus
@@ -236,7 +257,7 @@
 
     #[test]
     fn classify_dpad_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -249,7 +270,7 @@
 
     #[test]
     fn classify_joystick_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Joystick
@@ -262,7 +283,7 @@
 
     #[test]
     fn classify_gamepad_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Gamepad
@@ -275,7 +296,7 @@
 
     #[test]
     fn reclassify_keyboard_on_alphabetic_key_event() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -293,7 +314,7 @@
 
     #[test]
     fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -311,7 +332,7 @@
 
     #[test]
     fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -326,20 +347,82 @@
         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: RustInputDeviceIdentifier {
-                name: "test_device".to_string(),
-                location: "location".to_string(),
-                unique_id: "unique_id".to_string(),
-                bus: 123,
-                vendor: 234,
-                product: 345,
-                version: 567,
-                descriptor: "descriptor".to_string(),
-            },
+            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 5010475..4f4ea85 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -16,17 +16,22 @@
 
 //! The rust component of libinput.
 
+mod data_store;
 mod input;
 mod input_verifier;
+mod keyboard_classification_config;
 mod keyboard_classifier;
 
+pub use data_store::{DataStore, DefaultFileReaderWriter};
 pub use input::{
-    DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source,
+    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"]
@@ -148,7 +153,14 @@
 }
 
 fn create_keyboard_classifier() -> Box<KeyboardClassifier> {
-    Box::new(KeyboardClassifier::new())
+    // 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(
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e9d799e..e04236b 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -16,6 +16,7 @@
         "BlockingQueue_test.cpp",
         "IdGenerator_test.cpp",
         "InputChannel_test.cpp",
+        "InputConsumer_test.cpp",
         "InputDevice_test.cpp",
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
@@ -23,7 +24,9 @@
         "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",
@@ -60,6 +63,7 @@
         },
     },
     shared_libs: [
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
         "libcutils",
@@ -91,6 +95,7 @@
             },
         },
     },
+    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 02d4c07..25356cf 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -65,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 = {};
@@ -78,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)
@@ -94,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)
@@ -134,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)
@@ -156,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";
 }
 
@@ -172,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";
 }
 
@@ -207,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;
@@ -218,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 3717f49..a67e1ef 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -371,8 +371,8 @@
                       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);
+    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) {
@@ -591,6 +591,22 @@
     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)
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index f49469c..1210f71 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -52,7 +52,7 @@
     const int32_t action;
     const nsecs_t downTime;
     const uint32_t seq;
-    const int32_t eventId;
+    int32_t eventId;
     const int32_t deviceId = 1;
     const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
     const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
@@ -291,6 +291,7 @@
     void publishAndConsumeKeyEvent();
     void publishAndConsumeMotionStream();
     void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeSinglePointerMultipleSamples(const size_t nSamples);
     void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
     void publishAndConsumeFocusEvent();
     void publishAndConsumeCaptureEvent();
@@ -298,6 +299,7 @@
     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);
@@ -362,7 +364,7 @@
         if (!mConsumer->probablyHasInput()) {
             ADD_FAILURE() << "should deterministically have input because there is a batch";
         }
-        mConsumer->consumeBatchedInputEvents(std::nullopt);
+        mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt);
     };
     void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
         mFocusEvents.push(std::move(event));
@@ -394,8 +396,9 @@
             break;
         }
         case LooperMessage::CREATE_CONSUMER: {
-            mConsumer = std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel),
-                                                                    mLooper, *this);
+            mConsumer =
+                    std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel), mLooper,
+                                                                *this, /*resampler=*/nullptr);
             break;
         }
         case LooperMessage::DESTROY_CONSUMER: {
@@ -519,6 +522,123 @@
                                  {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++;
@@ -814,4 +934,8 @@
     ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
 }
 
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishAndConsumeSinglePointer) {
+    publishAndConsumeSinglePointerMultipleSamples(3);
+}
+
 } // namespace android
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index cc41eeb..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;
 }
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index d077760..106e686 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -70,7 +70,7 @@
 }
 
 TEST(JerkTrackerTest, JerkReadiness) {
-    JerkTracker jerkTracker(true);
+    JerkTracker jerkTracker(/*normalizedDt=*/true, /*alpha=*/1);
     EXPECT_FALSE(jerkTracker.jerkMagnitude());
     jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
     EXPECT_FALSE(jerkTracker.jerkMagnitude());
@@ -87,7 +87,8 @@
 }
 
 TEST(JerkTrackerTest, JerkCalculationNormalizedDtTrue) {
-    JerkTracker jerkTracker(true);
+    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);
@@ -118,11 +119,13 @@
      * y'':  3 -> -15
      * y''': -18
      */
-    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(-50, -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) {
-    JerkTracker jerkTracker(false);
+    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);
@@ -153,11 +156,12 @@
      * y'':  .03 -> -.125 (delta above, divide by 10)
      * y''': -.0155 (delta above, divide by 10)
      */
-    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(-.0375, -.0155));
+    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(true);
+    JerkTracker jerkTracker(/*normalizedDt=*/true, /*alpha=*/1);
     jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
     jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
     jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
@@ -291,15 +295,22 @@
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
 
-    // Jerk is medium (1.05 normalized, which is halfway between LOW_JANK and HIGH_JANK)
-    predictor.record(getMotionEvent(DOWN, 0, 5.2, 20ms));
-    predictor.record(getMotionEvent(MOVE, 0, 11.5, 30ms));
-    predictor.record(getMotionEvent(MOVE, 0, 22, 40ms));
-    predictor.record(getMotionEvent(MOVE, 0, 37.75, 50ms));
-    predictor.record(getMotionEvent(MOVE, 0, 59.8, 60ms));
+    // 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_JANK and HIGH_JANK means that half of the predictions
+    // 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.
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/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/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/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/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 4a04467..d248ea0 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -100,11 +100,14 @@
         "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..17ab29f 100644
--- a/libs/renderengine/OWNERS
+++ b/libs/renderengine/OWNERS
@@ -5,4 +5,6 @@
 djsollen@google.com
 jreck@google.com
 lpy@google.com
+nscobie@google.com
+sallyqi@google.com
 scroggo@google.com
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index bc3976d..907590a 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -21,6 +21,7 @@
 #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>
@@ -101,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 05a2063..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)
-                        .setBlurAlgorithm(renderengine::RenderEngine::BlurAlgorithm::KAWASE)
+                        .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/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index b640983..280ec19 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -102,6 +102,9 @@
         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) {
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 7207394..0fd982e 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -97,6 +97,7 @@
     bool cacheImageDimmedLayers = true;
     bool cacheClippedLayers = true;
     bool cacheShadowLayers = true;
+    bool cacheEdgeExtension = true;
     bool cachePIPImageLayers = true;
     bool cacheTransparentImageDimmedLayers = true;
     bool cacheClippedDimmedImageLayers = true;
@@ -131,6 +132,7 @@
         NONE,
         GAUSSIAN,
         KAWASE,
+        KAWASE_DUAL_FILTER,
     };
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
@@ -207,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
@@ -284,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;
 
@@ -293,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 {
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index a8c242a..fb8331d 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -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 8aeef9f..b7b7a4d 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -25,8 +25,8 @@
 
 #include "compat/SkiaBackendTexture.h"
 
+#include <common/trace.h>
 #include <log/log_main.h>
-#include <utils/Trace.h>
 
 namespace android {
 namespace renderengine {
@@ -63,7 +63,7 @@
 }
 
 sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     sk_sp<SkImage> image = mBackendTexture->makeImage(alphaType, dataspace, releaseImageProc, this);
     // The following ref will be counteracted by releaseProc, when SkImage is discarded.
@@ -75,7 +75,7 @@
 }
 
 sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     LOG_ALWAYS_FATAL_IF(!mBackendTexture->isOutputBuffer(),
                         "You can't generate an SkSurface for a read-only texture");
     if (!mSurface.get() || mDataspace != dataspace) {
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 74daf47..a570ad0 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -16,9 +16,9 @@
 
 #pragma once
 
-#include <GrDirectContext.h>
 #include <SkImage.h>
 #include <SkSurface.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
 #include <sys/types.h>
 #include <ui/GraphicTypes.h>
 
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 59b0656..57041ee 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 {
@@ -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
@@ -761,6 +789,12 @@
                 // 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);
+            }
         }
 
         if (config.cachePIPImageLayers) {
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
index 68798bf..a3a43e2 100644
--- a/libs/renderengine/skia/GaneshVkRenderEngine.cpp
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
@@ -21,9 +21,9 @@
 
 #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 
+#include <common/trace.h>
 #include <log/log_main.h>
 #include <sync/sync.h>
-#include <utils/Trace.h>
 
 namespace android::renderengine::skia {
 
@@ -78,7 +78,7 @@
                                                      sk_sp<SkSurface> dstSurface) {
     sk_sp<GrDirectContext> grContext = context->grDirectContext();
     {
-        ATRACE_NAME("flush surface");
+        SFTRACE_NAME("flush surface");
         // TODO: Investigate feasibility of combining this "surface flush" into the "context flush"
         // below.
         context->grDirectContext()->flush(dstSurface.get());
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
index b5cb21b..390ad6e 100644
--- a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
@@ -23,6 +23,7 @@
 #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>
@@ -77,7 +78,7 @@
     base::unique_fd fenceDup(dupedFd);
     VkSemaphore waitSemaphore =
             getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
-    graphite::BackendSemaphore beSemaphore(waitSemaphore);
+    auto beSemaphore = graphite::BackendSemaphores::MakeVulkan(waitSemaphore);
     mStagedWaitSemaphores.push_back(beSemaphore);
 }
 
@@ -92,7 +93,7 @@
     // 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();
-    graphite::BackendSemaphore backendSignalSemaphore(vkSignalSemaphore);
+    auto backendSignalSemaphore = graphite::BackendSemaphores::MakeVulkan(vkSignalSemaphore);
 
     // Collect all Vk semaphores that DestroySemaphoreInfo needs to own and delete after GPU work.
     std::vector<VkSemaphore> vkSemaphoresToCleanUp;
@@ -100,7 +101,8 @@
         vkSemaphoresToCleanUp.push_back(vkSignalSemaphore);
     }
     for (auto backendWaitSemaphore : mStagedWaitSemaphores) {
-        vkSemaphoresToCleanUp.push_back(backendWaitSemaphore.getVkSemaphore());
+        vkSemaphoresToCleanUp.push_back(
+            graphite::BackendSemaphores::GetVkSemaphore(backendWaitSemaphore));
     }
 
     DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 48270e1..4ef7d5b 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -21,19 +21,17 @@
 
 #include "SkiaGLRenderEngine.h"
 
-#include "compat/SkiaGpuContext.h"
-
 #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>
@@ -41,7 +39,7 @@
 #include <numeric>
 
 #include "GLExtensions.h"
-#include "log/log_main.h"
+#include "compat/SkiaGpuContext.h"
 
 namespace android {
 namespace renderengine {
@@ -332,7 +330,7 @@
 
 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);
     }
 }
@@ -341,19 +339,19 @@
                                                    sk_sp<SkSurface> dstSurface) {
     sk_sp<GrDirectContext> grContext = context->grDirectContext();
     {
-        ATRACE_NAME("flush surface");
+        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();
+    SFTRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
         // Chances are, something illegal happened (Skia's internal GPU object
@@ -400,7 +398,7 @@
 }
 
 base::unique_fd SkiaGLRenderEngine::flushGL() {
-    ATRACE_CALL();
+    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 bd177e6..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"
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index e62640e..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>
@@ -76,7 +75,9 @@
 #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"
@@ -238,12 +239,22 @@
 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(PrimeCacheConfig config) {
@@ -261,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() {
@@ -286,6 +297,11 @@
             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;
@@ -416,7 +432,7 @@
     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
@@ -441,7 +457,7 @@
 }
 
 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()) {
@@ -498,7 +514,7 @@
 }
 
 void SkiaRenderEngine::cleanupPostRender() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     mTextureCleanupMgr.cleanup();
 }
@@ -507,16 +523,24 @@
         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 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) {
@@ -525,21 +549,28 @@
                           static_cast<ui::PixelFormat>(targetBuffer->getPixelFormat()))
                 : std::nullopt;
 
-        if (parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local) {
-            // TODO: Handle color matrix transforms in linear space.
-            SkImage* image = parameters.shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
-            if (image) {
-                static MouriMap kMapper;
-                const float ratio = getHdrRenderType(parameters.layer.sourceDataspace, format) ==
-                                HdrRenderType::GENERIC_HDR
-                        ? 1.0f
-                        : parameters.layerDimmingRatio;
-                return kMapper.mouriMap(getActiveContext(), parameters.shader, ratio);
-            }
+        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};
@@ -555,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) {
@@ -683,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);
 
@@ -775,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);
@@ -869,7 +900,7 @@
             // TODO(b/182216890): Filter out empty layers earlier
             if (blurRect.width() > 0 && blurRect.height() > 0) {
                 if (layer.backgroundBlurRadius > 0) {
-                    ATRACE_NAME("BackgroundBlur");
+                    SFTRACE_NAME("BackgroundBlur");
                     auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
                                                               blurInput, blurRect);
 
@@ -882,7 +913,7 @@
                 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(context, region.blurRadius, blurInput,
                                                       blurRect);
@@ -965,12 +996,12 @@
 
         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(context, layer.source.buffer.fence->get());
@@ -1032,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;
@@ -1096,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,
@@ -1111,7 +1144,8 @@
                                                   .requiresLinearEffect = requiresLinearEffect,
                                                   .layerDimmingRatio = layerDimmingRatio,
                                                   .outputDataSpace = display.outputDataspace,
-                                                  .fakeOutputDataspace = fakeDataspace}));
+                                                  .fakeOutputDataspace = fakeDataspace,
+                                                  .imageBounds = SkRect::MakeEmpty()}));
         }
 
         if (layer.disableBlending) {
@@ -1152,7 +1186,7 @@
             canvas->drawRect(bounds.rect(), paint);
         }
         if (kGaneshFlushAfterEveryLayer) {
-            ATRACE_NAME("flush surface");
+            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
@@ -1166,11 +1200,48 @@
 
     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));
 }
 
@@ -1185,7 +1256,7 @@
 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;
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index c8f9241..b5f8898 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -18,11 +18,12 @@
 #define SF_SKIARENDERENGINE_H_
 
 #include <renderengine/RenderEngine.h>
-#include <sys/types.h>
 
-#include <GrBackendSemaphore.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>
@@ -32,12 +33,11 @@
 #include <unordered_map>
 
 #include "AutoBackendTexture.h"
-#include "GrContextOptions.h"
-#include "SkImageInfo.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"
 
@@ -142,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;
 
@@ -156,6 +163,7 @@
         float layerDimmingRatio;
         const ui::Dataspace outputDataSpace;
         const ui::Dataspace fakeOutputDataspace;
+        const SkRect& imageBounds;
     };
     sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
@@ -175,6 +183,7 @@
     AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
 
     StretchShaderFactory mStretchShaderFactory;
+    EdgeExtensionShaderFactory mEdgeExtensionShaderFactory;
 
     sp<Fence> mLastDrawFence;
     BlurFilter* mBlurFilter = nullptr;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index bd50107..677a2b6 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -24,18 +24,16 @@
 #include "GaneshVkRenderEngine.h"
 #include "compat/SkiaGpuContext.h"
 
-#include <GrBackendSemaphore.h>
-#include <GrContextOptions.h>
-#include <GrDirectContext.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 <vk/GrVkExtensions.h>
-#include <vk/GrVkTypes.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 <memory>
 #include <string>
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 0a2f9b2..d2bb3d5 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -17,8 +17,6 @@
 #ifndef SF_SKIAVKRENDERENGINE_H_
 #define SF_SKIAVKRENDERENGINE_H_
 
-#include <vk/GrVkBackendContext.h>
-
 #include "SkiaRenderEngine.h"
 #include "VulkanInterface.h"
 #include "compat/SkiaGpuContext.h"
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 5e756b0..37b69f6 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -32,21 +32,8 @@
 namespace renderengine {
 namespace skia {
 
-GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
-    GrVkBackendContext backendContext;
-    backendContext.fInstance = mInstance;
-    backendContext.fPhysicalDevice = mPhysicalDevice;
-    backendContext.fDevice = mDevice;
-    backendContext.fQueue = mQueue;
-    backendContext.fGraphicsQueueIndex = mQueueIndex;
-    backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
-    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;
+VulkanBackendContext VulkanInterface::getGaneshBackendContext() {
+    return this->getGraphiteBackendContext();
 };
 
 VulkanBackendContext VulkanInterface::getGraphiteBackendContext() {
@@ -57,7 +44,7 @@
     backendContext.fQueue = mQueue;
     backendContext.fGraphicsQueueIndex = mQueueIndex;
     backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
+    backendContext.fVkExtensions = &mVulkanExtensions;
     backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
     backendContext.fGetProc = mGrGetProc;
     backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
@@ -429,11 +416,11 @@
         mDeviceExtensionNames.push_back(devExt.extensionName);
     }
 
-    mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
-                       enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
-                       enabledDeviceExtensionNames.data());
+    mVulkanExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+                           enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+                           enabledDeviceExtensionNames.data());
 
-    if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+    if (!mVulkanExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
         BAIL("Vulkan driver doesn't support external semaphore fd");
     }
 
@@ -458,7 +445,7 @@
         tailPnext = &mProtectedMemoryFeatures->pNext;
     }
 
-    if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+    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;
@@ -484,7 +471,7 @@
             queuePriority,
     };
 
-    if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
         queueNextPtr = &queuePriorityCreateInfo;
     }
 
@@ -606,7 +593,7 @@
     mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
     mQueueIndex = 0;
     mApiVersion = 0;
-    mGrExtensions = skgpu::VulkanExtensions();
+    mVulkanExtensions = skgpu::VulkanExtensions();
     mGrGetProc = nullptr;
     mIsProtected = false;
     mIsRealtimePriority = false;
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index f20b002..d0fe4d1 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 #include <include/gpu/vk/VulkanExtensions.h>
 #include <include/gpu/vk/VulkanTypes.h>
 
@@ -24,10 +24,6 @@
 
 using namespace skgpu;
 
-namespace skgpu {
-struct VulkanBackendContext;
-} // namespace skgpu
-
 namespace android {
 namespace renderengine {
 namespace skia {
@@ -48,7 +44,8 @@
     bool takeOwnership();
     void teardown();
 
-    GrVkBackendContext getGaneshBackendContext();
+    // TODO(b/309785258) Combine these into one now that they are the same implementation.
+    VulkanBackendContext getGaneshBackendContext();
     VulkanBackendContext getGraphiteBackendContext();
     VkSemaphore createExportableSemaphore();
     VkSemaphore importSemaphoreFromSyncFd(int syncFd);
@@ -86,7 +83,7 @@
     VkQueue mQueue = VK_NULL_HANDLE;
     int mQueueIndex = 0;
     uint32_t mApiVersion = 0;
-    skgpu::VulkanExtensions mGrExtensions;
+    skgpu::VulkanExtensions mVulkanExtensions;
     VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
     VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
     VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.cpp b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
index d246466..88282e7 100644
--- a/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
@@ -21,26 +21,26 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <include/core/SkImage.h>
-#include <include/gpu/GrDirectContext.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/vk/GrVkTypes.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>
-#include <utils/Trace.h>
 
 namespace android::renderengine::skia {
 
 GaneshBackendTexture::GaneshBackendTexture(sk_sp<GrDirectContext> grContext,
                                            AHardwareBuffer* buffer, bool isOutputBuffer)
       : SkiaBackendTexture(buffer, isOutputBuffer), mGrContext(grContext) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.h b/libs/renderengine/skia/compat/GaneshBackendTexture.h
index 5cf8647..4337df1 100644
--- a/libs/renderengine/skia/compat/GaneshBackendTexture.h
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.h
@@ -21,7 +21,7 @@
 
 #include <include/android/GrAHardwareBufferUtils.h>
 #include <include/core/SkColorSpace.h>
-#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
 
 #include <android-base/macros.h>
 
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
index b2eae00..931f843 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -19,13 +19,13 @@
 #include <include/core/SkImageInfo.h>
 #include <include/core/SkSurface.h>
 #include <include/core/SkTraceMemoryDump.h>
-#include <include/gpu/GrDirectContext.h>
-#include <include/gpu/GrTypes.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/gl/GrGLInterface.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 
 #include "../AutoBackendTexture.h"
 #include "GaneshBackendTexture.h"
@@ -56,10 +56,10 @@
 }
 
 std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
-        const GrVkBackendContext& grVkBackendContext,
+        const skgpu::VulkanBackendContext& vkBackendContext,
         GrContextOptions::PersistentCache& skSLCacheMonitor) {
     return std::make_unique<GaneshGpuContext>(
-            GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor)));
+            GrDirectContexts::MakeVulkan(vkBackendContext, ganeshOptions(skSLCacheMonitor)));
 }
 
 GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
index 3dd9ed2..a6e93ba 100644
--- a/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
+++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
@@ -28,16 +28,16 @@
 #include "skia/ColorSpaces.h"
 
 #include <android/hardware_buffer.h>
+#include <common/trace.h>
 #include <inttypes.h>
 #include <log/log_main.h>
-#include <utils/Trace.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)) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
diff --git a/libs/renderengine/skia/compat/SkiaBackendTexture.h b/libs/renderengine/skia/compat/SkiaBackendTexture.h
index 09877a5..fa12624 100644
--- a/libs/renderengine/skia/compat/SkiaBackendTexture.h
+++ b/libs/renderengine/skia/compat/SkiaBackendTexture.h
@@ -18,7 +18,7 @@
 
 #include <include/android/GrAHardwareBufferUtils.h>
 #include <include/core/SkColorSpace.h>
-#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
 
 #include <android/hardware_buffer.h>
 #include <ui/GraphicTypes.h>
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
index 282dfe7..0bd8283 100644
--- a/libs/renderengine/skia/compat/SkiaGpuContext.h
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -20,11 +20,10 @@
 #define LOG_TAG "RenderEngine"
 
 #include <include/core/SkSurface.h>
-#include <include/gpu/GrDirectContext.h>
-#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+#include <include/gpu/ganesh/gl/GrGLInterface.h>
 #include <include/gpu/graphite/Context.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
-#include "include/gpu/vk/VulkanBackendContext.h"
+#include <include/gpu/vk/VulkanBackendContext.h>
 
 #include "SkiaBackendTexture.h"
 
@@ -52,10 +51,10 @@
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     /**
-     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     * vkBackendContext must remain valid until after SkiaGpuContext is destroyed.
      */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
-            const GrVkBackendContext& grVkBackendContext,
+            const skgpu::VulkanBackendContext& vkBackendContext,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
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 e778884..e6a0e22 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -22,9 +22,9 @@
 
 #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"
@@ -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, "");
 
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/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 c9499cb..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 {
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 7a070d7..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 {
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
index 7d8b8a5..b099bcf 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -35,7 +35,7 @@
         float maximum = 0.0;
         for (int y = 0; y < 16; y++) {
             for (int x = 0; x < 16; x++) {
-                float3 linear = toLinearSrgb(bitmap.eval(xy * 16 + vec2(x, y)).rgb) * hdrSdrRatio;
+                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)));
             }
@@ -49,7 +49,7 @@
         float maximum = 0.0;
         for (int y = 0; y < 8; y++) {
             for (int x = 0; x < 8; x++) {
-                maximum = max(maximum, bitmap.eval(xy * 8 + vec2(x, y)).r);
+                maximum = max(maximum, bitmap.eval((xy - 0.5) * 8 + 0.5 + vec2(x, y)).r);
             }
         }
         return float4(float3(maximum), 1.0);
@@ -67,7 +67,7 @@
         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;
+                result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r;
             }
         }
         return float4(float3(exp2(result)), 1.0);
@@ -78,19 +78,21 @@
     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 <= 1.0) {
-            return float4(fromLinearSrgb(linear), 1.0);
+        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 / (localMax * localMax)) / (1 + maxRGB);
-        return float4(fromLinearSrgb(linear * gain), 1.0);
+        float gain = (1 + maxRGB * (targetHdrSdrRatio / (localMax * localMax)))
+                / (1 + maxRGB / targetHdrSdrRatio);
+        return float4(fromLinearSrgb(linear * gain), rgba.a);
     }
 )");
 
@@ -114,10 +116,10 @@
         mTonemap(makeEffect(kTonemap)) {}
 
 sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
-                                   float hdrSdrRatio) {
+                                   float hdrSdrRatio, float targetHdrSdrRatio) {
     auto downchunked = downchunk(context, input, hdrSdrRatio);
     auto localLux = blur(context, downchunked.get());
-    return tonemap(input, localLux.get(), hdrSdrRatio);
+    return tonemap(input, localLux.get(), hdrSdrRatio, targetHdrSdrRatio);
 }
 
 sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
@@ -166,8 +168,8 @@
     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) const {
+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;
@@ -176,8 +178,9 @@
                                     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
\ No newline at end of file
+} // namespace android
diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h
index 3c0df8a..9ba2b6f 100644
--- a/libs/renderengine/skia/filters/MouriMap.h
+++ b/libs/renderengine/skia/filters/MouriMap.h
@@ -64,13 +64,16 @@
     // 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.
-    sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float hdrSdrRatio);
+    // 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) 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;
@@ -78,4 +81,4 @@
 };
 } // namespace skia
 } // namespace renderengine
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index 0783714..7fbbf49 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -66,5 +66,6 @@
         "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 a8a9823..b5cc65f 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -34,9 +34,12 @@
 #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"
@@ -259,22 +262,51 @@
 
     ~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();
         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;
         }
 
@@ -304,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();
     }
 
@@ -3147,6 +3180,214 @@
     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) {
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index d27c151..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!");
@@ -133,13 +133,13 @@
 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, config](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::primeCache");
+            SFTRACE_NAME("REThreaded::primeCache");
             if (setSchedFifo(false) != NO_ERROR) {
                 ALOGW("Couldn't set SCHED_OTHER for primeCache");
             }
@@ -163,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));
@@ -176,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);
         });
     }
@@ -190,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));
                 });
     }
@@ -229,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;
@@ -249,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();
@@ -261,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));
                 });
@@ -271,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);
         });
@@ -297,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);
         });
     }
@@ -324,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 d4997d6..cb6e924 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -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/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
index 3a4c869..b5c56c5 100644
--- a/libs/tracing_perfetto/Android.bp
+++ b/libs/tracing_perfetto/Android.bp
@@ -40,6 +40,7 @@
     ],
 
     shared_libs: [
+        "libbase",
         "libcutils",
         "libperfetto_c",
         "android.os.flags-aconfig-cc-host",
diff --git a/libs/tracing_perfetto/include/trace_result.h b/libs/tracing_perfetto/include/trace_result.h
deleted file mode 100644
index f7581fc..0000000
--- a/libs/tracing_perfetto/include/trace_result.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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_RESULT_H
-#define TRACE_RESULT_H
-
-namespace tracing_perfetto {
-
-enum class Result {
-  SUCCESS,
-  NOT_SUPPORTED,
-  INVALID_INPUT,
-};
-
-}
-
-#endif  // TRACE_RESULT_H
diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h
index 2c1c2a4..59c43d6 100644
--- a/libs/tracing_perfetto/include/tracing_perfetto.h
+++ b/libs/tracing_perfetto/include/tracing_perfetto.h
@@ -14,40 +14,40 @@
  * limitations under the License.
  */
 
-#ifndef TRACING_PERFETTO_H
-#define TRACING_PERFETTO_H
+#pragma once
 
 #include <stdint.h>
 
-#include "trace_result.h"
-
 namespace tracing_perfetto {
 
 void registerWithPerfetto(bool test = false);
 
-Result traceBegin(uint64_t category, const char* name);
+void traceBegin(uint64_t category, const char* name);
 
-Result traceEnd(uint64_t category);
+void traceEnd(uint64_t category);
 
-Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie);
+void traceAsyncBegin(uint64_t category, const char* name, int32_t cookie);
 
-Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie);
+void traceFormatBegin(uint64_t category, const char* fmt, ...);
 
-Result traceAsyncBeginForTrack(uint64_t category, const char* name,
+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);
 
-Result traceAsyncEndForTrack(uint64_t category, const char* trackName,
+void traceAsyncEndForTrack(uint64_t category, const char* trackName,
                              int32_t cookie);
 
-Result traceInstant(uint64_t category, const char* name);
+void traceInstant(uint64_t category, const char* name);
 
-Result traceInstantForTrack(uint64_t category, const char* trackName,
+void traceFormatInstant(uint64_t category, const char* fmt, ...);
+
+void traceInstantForTrack(uint64_t category, const char* trackName,
                             const char* name);
 
-Result traceCounter(uint64_t category, const char* name, int64_t value);
+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
-
-#endif  // TRACING_PERFETTO_H
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
index a35b0e0..d203467 100644
--- a/libs/tracing_perfetto/tests/Android.bp
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -26,6 +26,7 @@
     static_libs: [
         "libflagtest",
         "libgmock",
+        "perfetto_trace_protos",
     ],
     cflags: [
         "-Wall",
@@ -35,6 +36,8 @@
         "android.os.flags-aconfig-cc-host",
         "libbase",
         "libperfetto_c",
+        "liblog",
+        "libprotobuf-cpp-lite",
         "libtracing_perfetto",
     ],
     srcs: [
diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
index 7716b9a..e9fee2e 100644
--- a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
+++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
@@ -16,10 +16,10 @@
 
 #include "tracing_perfetto.h"
 
-#include <thread>
-
 #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"
@@ -45,67 +45,182 @@
 #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::shlib::test_utils::AllFieldsWithId;
-using ::perfetto::shlib::test_utils::FieldView;
-using ::perfetto::shlib::test_utils::IdFieldView;
-using ::perfetto::shlib::test_utils::MsgField;
-using ::perfetto::shlib::test_utils::PbField;
-using ::perfetto::shlib::test_utils::StringField;
+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;
-using ::perfetto::shlib::test_utils::VarIntField;
-using ::testing::_;
-using ::testing::ElementsAre;
-using ::testing::UnorderedElementsAre;
 
 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(true /* test */);
+    tracing_perfetto::registerWithPerfetto(false /* test */);
   }
 };
 
-// TODO(b/303199244): Add tests for all the library functions.
-
-TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstant,
-                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
-  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, "");
-
+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 (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
-    ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
-                                     MsgField(_)));
-    IdFieldView track_event(
-        trace_field, perfetto_protos_TracePacket_track_event_field_number);
-    if (track_event.size() == 0) {
-      continue;
+  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;
+      }
     }
-    found = true;
-    IdFieldView cat_iid_fields(
-        track_event.front(),
-        perfetto_protos_TrackEvent_category_iids_field_number);
-    ASSERT_THAT(cat_iid_fields, ElementsAre(VarIntField(_)));
-    uint64_t cat_iid = cat_iid_fields.front().value.integer64;
-    EXPECT_THAT(
-        trace_field,
-        AllFieldsWithId(
-            perfetto_protos_TracePacket_interned_data_field_number,
-            ElementsAre(AllFieldsWithId(
-                perfetto_protos_InternedData_event_categories_field_number,
-                ElementsAre(MsgField(UnorderedElementsAre(
-                    PbField(perfetto_protos_EventCategory_iid_field_number,
-                            VarIntField(cat_iid)),
-                    PbField(perfetto_protos_EventCategory_name_field_number,
-                            StringField("input")))))))));
   }
   EXPECT_TRUE(found);
 }
 
-}  // namespace tracing_perfetto
\ No newline at end of file
+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
index 9c42028..8c4d4a8 100644
--- a/libs/tracing_perfetto/tests/utils.cpp
+++ b/libs/tracing_perfetto/tests/utils.cpp
@@ -26,6 +26,11 @@
 #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 {
@@ -44,63 +49,54 @@
 }  // namespace
 
 TracingSession TracingSession::Builder::Build() {
-  struct PerfettoPbMsgWriter writer;
-  struct PerfettoHeapBuffer* hb = PerfettoHeapBufferCreate(&writer.writer);
+  perfetto::protos::TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);
 
-  struct perfetto_protos_TraceConfig cfg;
-  PerfettoPbMsgInit(&cfg.msg, &writer);
+  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);
 
   {
-    struct perfetto_protos_TraceConfig_BufferConfig buffers;
-    perfetto_protos_TraceConfig_begin_buffers(&cfg, &buffers);
-
-    perfetto_protos_TraceConfig_BufferConfig_set_size_kb(&buffers, 1024);
-
-    perfetto_protos_TraceConfig_end_buffers(&cfg, &buffers);
-  }
-
-  {
-    struct perfetto_protos_TraceConfig_DataSource data_sources;
-    perfetto_protos_TraceConfig_begin_data_sources(&cfg, &data_sources);
-
-    {
-      struct perfetto_protos_DataSourceConfig ds_cfg;
-      perfetto_protos_TraceConfig_DataSource_begin_config(&data_sources,
-                                                          &ds_cfg);
-
-      perfetto_protos_DataSourceConfig_set_cstr_name(&ds_cfg,
-                                                     data_source_name_.c_str());
-      if (!enabled_categories_.empty() && !disabled_categories_.empty()) {
-        perfetto_protos_TrackEventConfig te_cfg;
-        perfetto_protos_DataSourceConfig_begin_track_event_config(&ds_cfg,
-                                                                  &te_cfg);
-        for (const std::string& cat : enabled_categories_) {
-          perfetto_protos_TrackEventConfig_set_enabled_categories(
-              &te_cfg, cat.data(), cat.size());
-        }
-        for (const std::string& cat : disabled_categories_) {
-          perfetto_protos_TrackEventConfig_set_disabled_categories(
-              &te_cfg, cat.data(), cat.size());
-        }
-        perfetto_protos_DataSourceConfig_end_track_event_config(&ds_cfg,
-                                                                &te_cfg);
+    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);
       }
 
-      perfetto_protos_TraceConfig_DataSource_end_config(&data_sources, &ds_cfg);
+      for (const std::string& cat : atrace_categories_prefer_sdk_) {
+        ftrace_config->add_atrace_categories_prefer_sdk(cat);
+      }
     }
-
-    perfetto_protos_TraceConfig_end_data_sources(&cfg, &data_sources);
   }
-  size_t cfg_size = PerfettoStreamWriterGetWrittenSize(&writer.writer);
-  std::unique_ptr<uint8_t[]> ser(new uint8_t[cfg_size]);
-  PerfettoHeapBufferCopyInto(hb, &writer.writer, ser.get(), cfg_size);
-  PerfettoHeapBufferDestroy(hb, &writer.writer);
+
+  {
+    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_IN_PROCESS);
+      PerfettoTracingSessionCreate(PERFETTO_BACKEND_SYSTEM);
 
-  PerfettoTracingSessionSetup(ts, ser.get(), cfg_size);
+  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);
diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h
index 4353554..8edb414 100644
--- a/libs/tracing_perfetto/tests/utils.h
+++ b/libs/tracing_perfetto/tests/utils.h
@@ -74,10 +74,6 @@
   class Builder {
    public:
     Builder() = default;
-    Builder& set_data_source_name(std::string data_source_name) {
-      data_source_name_ = std::move(data_source_name);
-      return *this;
-    }
     Builder& add_enabled_category(std::string category) {
       enabled_categories_.push_back(std::move(category));
       return *this;
@@ -86,12 +82,21 @@
       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::string data_source_name_;
     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*);
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
index 6f716ee..c35e078 100644
--- a/libs/tracing_perfetto/tracing_perfetto.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -17,6 +17,7 @@
 #include "tracing_perfetto.h"
 
 #include <cutils/trace.h>
+#include <cstdarg>
 
 #include "perfetto/public/te_category_macros.h"
 #include "trace_categories.h"
@@ -28,116 +29,172 @@
   internal::registerWithPerfetto(test);
 }
 
-Result traceBegin(uint64_t category, const char* name) {
+void traceBegin(uint64_t category, const char* name) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceBegin(*perfettoTeCategory, name);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_begin(category, name);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceBegin(*perfettoTeCategory, name);
   }
 }
 
-Result traceEnd(uint64_t category) {
+void traceFormatBegin(uint64_t category, const char* fmt, ...) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceEnd(*perfettoTeCategory);
-  } else {
+  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);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceEnd(*perfettoTeCategory);
   }
 }
 
-Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie) {
+void traceAsyncBegin(uint64_t category, const char* name, int32_t cookie) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceAsyncBegin(*perfettoTeCategory, name, cookie);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_async_begin(category, name, cookie);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncBegin(*perfettoTeCategory, name, cookie);
   }
 }
 
-Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) {
+void traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceAsyncEnd(*perfettoTeCategory, name, cookie);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_async_end(category, name, cookie);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncEnd(*perfettoTeCategory, name, cookie);
   }
 }
 
-Result traceAsyncBeginForTrack(uint64_t category, const char* name,
+void traceAsyncBeginForTrack(uint64_t category, const char* name,
                                const char* trackName, int32_t cookie) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_async_for_track_begin(category, trackName, name, cookie);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
   }
 }
 
-Result traceAsyncEndForTrack(uint64_t category, const char* trackName,
+void traceAsyncEndForTrack(uint64_t category, const char* trackName,
                              int32_t cookie) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_async_for_track_end(category, trackName, cookie);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
   }
 }
 
-Result traceInstant(uint64_t category, const char* name) {
+void traceInstant(uint64_t category, const char* name) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceInstant(*perfettoTeCategory, name);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_instant(category, name);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceInstant(*perfettoTeCategory, name);
   }
 }
 
-Result traceInstantForTrack(uint64_t category, const char* trackName,
+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 (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceInstantForTrack(*perfettoTeCategory, trackName, name);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_instant_for_track(category, trackName, name);
-    return Result::SUCCESS;
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceInstantForTrack(*perfettoTeCategory, trackName, name);
   }
 }
 
-Result traceCounter(uint64_t category, const char* name, int64_t value) {
+void traceCounter(uint64_t category, const char* name, int64_t value) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  if (perfettoTeCategory != nullptr) {
-    return internal::perfettoTraceCounter(*perfettoTeCategory, name, value);
-  } else {
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_int64(category, name, value);
-    return Result::SUCCESS;
+  } 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);
-  if (perfettoTeCategory != nullptr) {
-    return true;
-  } else {
-    return (atrace_get_enabled_tags() & category) != 0;
-  }
+  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
index 758ace6..9a0042a 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -44,13 +44,13 @@
   C(rro, "rro", "RRO category")                                  \
   C(thermal, "thermal", "Thermal category")
 
-#include "tracing_perfetto_internal.h"
-
-#include <inttypes.h>
-
+#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"
@@ -58,19 +58,42 @@
 #include "perfetto/public/te_macros.h"
 #include "perfetto/public/track_event.h"
 #include "trace_categories.h"
-#include "trace_result.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);
 
-std::atomic_bool is_perfetto_registered = false;
+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) {
@@ -137,8 +160,60 @@
 
 }  // namespace
 
-bool isPerfettoRegistered() {
-  return is_perfetto_registered;
+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) {
@@ -148,7 +223,7 @@
   }
 
   bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
-      (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+       (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
   return enabled ? perfettoCategory : nullptr;
 }
 
@@ -164,70 +239,57 @@
     PerfettoProducerInit(args);
     PerfettoTeInit();
     PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
-    is_perfetto_registered = true;
   });
 }
 
-Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) {
+void perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) {
   PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN(name));
-  return Result::SUCCESS;
 }
 
-Result perfettoTraceEnd(const struct PerfettoTeCategory& category) {
+void perfettoTraceEnd(const struct PerfettoTeCategory& category) {
   PERFETTO_TE(category, PERFETTO_TE_SLICE_END());
-  return Result::SUCCESS;
 }
 
-Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+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()));
-  return Result::SUCCESS;
 }
 
-Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+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()));
-  return Result::SUCCESS;
 }
 
-Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
                                uint64_t cookie) {
-  return perfettoTraceAsyncBeginForTrack(category, name, name, cookie);
+  perfettoTraceAsyncBeginForTrack(category, name, name, cookie);
 }
 
-Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+void perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
                              uint64_t cookie) {
-  return perfettoTraceAsyncEndForTrack(category, name, cookie);
+  perfettoTraceAsyncEndForTrack(category, name, cookie);
 }
 
-Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) {
+void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) {
   PERFETTO_TE(category, PERFETTO_TE_INSTANT(name));
-  return Result::SUCCESS;
 }
 
-Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+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()));
-  return Result::SUCCESS;
 }
 
-Result perfettoTraceCounter(const struct PerfettoTeCategory& category,
+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));
-  return Result::SUCCESS;
 }
-
-uint64_t getDefaultCategories() {
-  return TRACE_CATEGORIES;
-}
-
 }  // namespace internal
 
 }  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h
index 79e4b8f..3e1ac2a 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.h
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.h
@@ -19,7 +19,6 @@
 
 #include <stdint.h>
 
-#include "include/trace_result.h"
 #include "perfetto/public/te_category_macros.h"
 
 namespace tracing_perfetto {
@@ -32,31 +31,33 @@
 
 void registerWithPerfetto(bool test = false);
 
-Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name);
+void perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name);
 
-Result perfettoTraceEnd(const struct PerfettoTeCategory& category);
+void perfettoTraceEnd(const struct PerfettoTeCategory& category);
 
-Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
                                uint64_t cookie);
 
-Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+void perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
                              uint64_t cookie);
 
-Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+void perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
                                        const char* trackName, uint64_t cookie);
 
-Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+void perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
                                      const char* trackName, uint64_t cookie);
 
-Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name);
+void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name);
 
-Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+void perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
                                     const char* trackName, const char* name);
 
-Result perfettoTraceCounter(const struct PerfettoTeCategory& category, const char* name,
+void perfettoTraceCounter(const struct PerfettoTeCategory& category, const char* name,
                             int64_t value);
 
-uint64_t getDefaultCategories();
+bool isPerfettoCategoryEnabled(PerfettoTeCategory *perfettoTeCategory);
+
+bool shouldPreferAtrace(PerfettoTeCategory *perfettoTeCategory, uint64_t category);
 
 }  // namespace internal
 
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index e5af740..8b13d78 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -26,6 +26,7 @@
 #include <ftl/hash.h>
 #include <log/log.h>
 #include <ui/DisplayIdentification.h>
+#include <ui/Size.h>
 
 namespace android {
 namespace {
@@ -46,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'));
@@ -219,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;
@@ -243,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);
@@ -297,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) {
@@ -336,9 +380,12 @@
     }
 
     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) {
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/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/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/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index 8bc2017..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&);
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/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 bbb2d77..97ed05a 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -137,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/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/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 721b466..76e3f66 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -194,6 +194,10 @@
     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);
@@ -206,6 +210,10 @@
     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);
@@ -224,6 +232,10 @@
     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);
@@ -242,6 +254,10 @@
     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);
@@ -260,6 +276,10 @@
     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);
@@ -273,6 +293,10 @@
     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) {
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/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/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/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 ec7b190..fed6afc 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -270,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());
     }
@@ -548,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/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/gpuservice/bpfprogs/Android.bp b/services/gpuservice/bpfprogs/Android.bp
index 680b291..a391c81 100644
--- a/services/gpuservice/bpfprogs/Android.bp
+++ b/services/gpuservice/bpfprogs/Android.bp
@@ -24,9 +24,4 @@
 bpf {
     name: "gpuMem.o",
     srcs: ["gpuMem.c"],
-    btf: true,
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
 }
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/Android.bp b/services/gpuservice/gpuwork/bpfprogs/Android.bp
index fe45c98..2e444fe 100644
--- a/services/gpuservice/gpuwork/bpfprogs/Android.bp
+++ b/services/gpuservice/gpuwork/bpfprogs/Android.bp
@@ -20,11 +20,7 @@
     name: "gpuWork.o",
     srcs: ["gpuWork.c"],
     cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wformat",
         "-Wthread-safety",
-        "-Wunused",
         "-Wunreachable-code",
     ],
 }
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/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/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/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 70801dc..ca92ab5 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -172,9 +172,8 @@
     export_static_lib_headers: [
         "libinputdispatcher",
     ],
-    export_include_dirs: [
-        ".",
-        "include",
+    export_shared_lib_headers: [
+        "libinputflinger_base",
     ],
 }
 
@@ -185,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 {
@@ -209,6 +217,7 @@
         "libcutils",
         "libinput",
         "liblog",
+        "libprocessgroup",
         "libstatslog",
         "libutils",
     ],
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 8e73ce5..2ef94fb 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -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/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 41e5247..b155122 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -250,6 +250,10 @@
         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/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 19a4f26..b2680a2 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -35,11 +35,6 @@
                                                              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,
@@ -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 00dd6ba..006d507 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -165,10 +165,6 @@
     mNextListener.notify(args);
 }
 
-void PointerChoreographer::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    mNextListener.notify(args);
-}
-
 void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
     fadeMouseCursorOnKeyPress(args);
     mNextListener.notify(args);
@@ -202,6 +198,7 @@
     }
     auto it = mMousePointersByDisplay.find(targetDisplay);
     if (it != mMousePointersByDisplay.end()) {
+        mPolicy.notifyMouseCursorFadedOnTyping();
         it->second->fade(PointerControllerInterface::Transition::GRADUAL);
     }
 }
@@ -367,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;
@@ -409,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);
     }
@@ -513,8 +512,9 @@
     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";
@@ -793,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;
         }
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index aaf1e3e..635487b 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -105,7 +105,6 @@
     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;
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index a4dd909..43eaf67 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -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",
@@ -149,16 +74,14 @@
     },
     {
       "name": "monkey_test"
+    },
+    {
+      "name": "CtsSurfaceControlTests"
     }
   ],
   "postsubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
-      "options": [
-        {
-          "include-filter": "android.server.wm.window.WindowInputTests"
-        }
-      ]
+      "name": "CtsWindowManagerDeviceWindow_window_windowinputtests"
     },
     {
       "name": "libinput_tests"
@@ -184,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",
@@ -290,11 +146,19 @@
     },
     {
       "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/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 4a0889f..568d348 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -32,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.
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 ad9cec1..ff407af 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -68,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)
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index f2f31d8..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;
 
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/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f9fbfef..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"
@@ -558,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:
@@ -694,7 +694,8 @@
  */
 std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
                                                     const TouchState& newTouchState,
-                                                    const MotionEntry& entry) {
+                                                    const MotionEntry& entry,
+                                                    std::function<void()> dump) {
     const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
 
     if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
@@ -742,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;
         }
@@ -873,6 +876,8 @@
             return {false, true};
         case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
             return {false, true};
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return {true, false};
     }
 }
 
@@ -901,6 +906,24 @@
     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 ---
@@ -1153,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);
@@ -1308,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());
@@ -1392,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:
@@ -1463,15 +1477,27 @@
 }
 
 std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
-        ui::LogicalDisplayId 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.
@@ -1554,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;
@@ -1643,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) {
@@ -1923,20 +1930,21 @@
     }
 
     // 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);
@@ -2041,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) {
@@ -2114,19 +2131,16 @@
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         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.toString().c_str(),
               entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
-              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
-              entry.xPrecision, entry.yPrecision, entry.downTime);
+              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),
@@ -2221,7 +2235,6 @@
         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: {
@@ -2263,11 +2276,9 @@
     return false;
 }
 
-sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
-        nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
-        InputEventInjectionResult& outInjectionResult) {
-    outInjectionResult = InputEventInjectionResult::FAILED; // Default result
-
+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 =
@@ -2279,12 +2290,12 @@
         ALOGI("Dropping %s event because there is no focused window or focused application in "
               "display %s.",
               ftl::enum_string(entry.type).c_str(), displayId.toString().c_str());
-        return nullptr;
+        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.
@@ -2304,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);
         }
     }
 
@@ -2324,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
@@ -2349,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;
 }
 
@@ -2378,9 +2383,8 @@
     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;
@@ -2390,9 +2394,6 @@
     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.
@@ -2432,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.
@@ -2454,20 +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 %s", x, y,
-                  displayId.toString().c_str());
-            // Try to assign the pointer to the first foreground window we find, if there is one.
-            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
+        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.
@@ -2490,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 "
-                  "%s.",
-                  x, y, displayId.toString().c_str());
-            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) {
@@ -2511,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.
@@ -2601,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.
@@ -2613,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);
         }
@@ -2635,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
@@ -2689,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);
             }
@@ -2716,7 +2717,9 @@
     // 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);
@@ -2739,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);
         }
     }
 
@@ -2797,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
@@ -2808,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) {
@@ -2870,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;
     }
 
@@ -3621,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());
@@ -3834,6 +3832,10 @@
                 }
                 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) {
                     ensureEventTraced(motionEntry);
                     mTracer->traceEventDispatch(*dispatchEntry, *motionEntry.traceTracker);
@@ -3878,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",
@@ -4274,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",
@@ -4357,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:
@@ -4443,28 +4442,11 @@
 
 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
@@ -4514,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();
 
@@ -4552,13 +4538,12 @@
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" 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, 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.toString().c_str(), args.policyFlags,
               MotionEvent::actionToString(args.action).c_str(), args.actionButton, args.flags,
-              args.metaState, args.buttonState, args.edgeFlags, args.xPrecision, args.yPrecision,
-              args.xCursorPosition, args.yCursorPosition, args.downTime);
+              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, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
@@ -4667,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));
@@ -4908,6 +4892,23 @@
                     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;
+                    }
                 }
             }
 
@@ -5436,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
@@ -5680,7 +5707,7 @@
     mMaximumObscuringOpacityForTouch = opacity;
 }
 
-std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId /*displayId*/>
+std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
 InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
     for (auto& [displayId, state] : mTouchStatesByDisplay) {
         for (TouchedWindow& w : state.windows) {
@@ -5692,6 +5719,22 @@
     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,
                                            bool isDragDrop) {
     if (fromToken == toToken) {
@@ -5758,7 +5801,7 @@
             }
             // 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.
@@ -6028,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()) {
@@ -7032,6 +7070,13 @@
     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
@@ -7107,9 +7152,11 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, DeviceId 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);
@@ -7136,7 +7183,7 @@
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
                                         InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
-                                deviceId, pointers);
+                                deviceId, pointers, entry.eventTime);
     }
 }
 
@@ -7210,11 +7257,13 @@
 }
 
 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,
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index e2fc7a0..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;
@@ -154,8 +153,8 @@
     // 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, ui::LogicalDisplayId displayId,
                            DeviceId deviceId, int32_t pointerId) override;
@@ -258,7 +257,8 @@
             int32_t pointerId) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
-            ui::LogicalDisplayId 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(
             ui::LogicalDisplayId displayId) const REQUIRES(mLock);
@@ -446,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,
@@ -536,12 +534,11 @@
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
     ui::LogicalDisplayId 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);
+    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);
 
@@ -684,16 +681,21 @@
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
-    std::map<ui::LogicalDisplayId /*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,
             bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
-    std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId /*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);
@@ -707,11 +709,23 @@
 
     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, DeviceId 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,
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 dfbe02f..9b5a79b 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -638,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;
     }
@@ -647,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/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 0c9ad3c..2bf63be 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -112,17 +112,18 @@
 }
 
 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);
 }
 
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9d4bb3d..451d917 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -47,9 +47,10 @@
             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);
+            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);
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 1f86f66..fa5be1a 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -36,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 {
@@ -78,16 +85,18 @@
     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});
 }
 
 Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
@@ -173,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;
             }
         }
@@ -270,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()) {
@@ -279,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()) {
@@ -312,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 4f0ad16..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
@@ -69,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;
@@ -78,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 653f595..463a952 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -227,10 +227,11 @@
     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.
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 65fb76d..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(
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index cb525a4..96e619c 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -65,10 +65,10 @@
         void onEventProcessingComplete(nsecs_t processingTimestamp);
 
         InputTracer& tracer;
-        std::vector<const TracedEvent> events;
+        std::vector<TracedEvent> events;
         bool isEventProcessingComplete{false};
         // A queue to hold dispatch args from being traced until event processing is complete.
-        std::vector<const WindowDispatchArgs> pendingDispatchArgs;
+        std::vector<WindowDispatchArgs> pendingDispatchArgs;
         // The metadata should not be modified after event processing is complete.
         TracedEventMetadata metadata{};
     };
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 889ee09..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.
@@ -91,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,
     };
@@ -227,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;
@@ -241,6 +249,9 @@
     // 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),
@@ -268,6 +279,7 @@
             touchpadNaturalScrollingEnabled(true),
             touchpadTapToClickEnabled(true),
             touchpadTapDraggingEnabled(false),
+            shouldNotifyTouchpadHardwareState(false),
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false) {}
@@ -327,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 */
@@ -363,6 +372,8 @@
 
     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, ui::LogicalDisplayId displayId) = 0;
 
@@ -397,6 +408,9 @@
      * 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 ---
@@ -451,6 +465,13 @@
      */
     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,
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 db417cf..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;
@@ -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/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 7a85c12..e1f8fda 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -58,6 +58,9 @@
 
     /* 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 e34ed0f..8f3d9ca 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -72,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;
 
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 fe70a51..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";
@@ -513,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";
     }
@@ -645,7 +649,6 @@
             continue;
         }
         auto& [axisInfo, value] = absState[axis];
-        axisInfo.valid = true;
         axisInfo.minValue = info.minimum;
         axisInfo.maxValue = info.maximum;
         axisInfo.flat = info.flat;
@@ -885,7 +888,6 @@
       : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
         mNextDeviceId(1),
         mControllerNumbers(),
-        mNeedToSendFinishedDeviceScan(false),
         mNeedToReopenDevices(false),
         mNeedToScanDevices(true),
         mPendingEventCount(0),
@@ -998,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 {
@@ -1130,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,
@@ -1180,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) {
@@ -1188,7 +1186,7 @@
     }
     const std::shared_ptr<KeyCharacterMap> kcm = device->getKeyCharacterMap();
     if (kcm) {
-        kcm->addKeyRemapping(fromKeyCode, toKeyCode);
+        kcm->setKeyRemapping(keyRemapping);
     }
 }
 
@@ -1879,7 +1877,6 @@
                     .type = DEVICE_REMOVED,
             });
             it = mClosingDevices.erase(it);
-            mNeedToSendFinishedDeviceScan = true;
             if (events.size() == EVENT_BUFFER_SIZE) {
                 break;
             }
@@ -1888,7 +1885,6 @@
         if (mNeedToScanDevices) {
             mNeedToScanDevices = false;
             scanDevicesLocked();
-            mNeedToSendFinishedDeviceScan = true;
         }
 
         while (!mOpeningDevices.empty()) {
@@ -1917,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;
             }
@@ -2503,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 2daf195..6185f1a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -365,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;
 }
@@ -689,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();
 }
@@ -725,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); });
 }
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index ab13ad4..e579390 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -33,6 +33,7 @@
 #include <utils/Thread.h>
 
 #include "InputDevice.h"
+#include "include/gestures.h"
 
 using android::base::StringPrintf;
 
@@ -180,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(
@@ -247,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;
@@ -414,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);
@@ -632,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);
 
@@ -825,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);
 
@@ -907,6 +903,12 @@
     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);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 7cf584d..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,18 +71,14 @@
 
 /* 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.
@@ -257,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,
     };
@@ -278,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;
 
@@ -287,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,
@@ -339,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,
@@ -511,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;
 
@@ -520,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,
@@ -559,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;
 
@@ -793,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 4374ff5..62cc4da 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -72,7 +72,7 @@
     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; }
@@ -124,8 +124,6 @@
     int32_t getMetaState();
     void updateMetaState(int32_t keyCode);
 
-    void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode);
-
     void setKeyboardType(KeyboardType keyboardType);
 
     void bumpGeneration();
@@ -141,6 +139,8 @@
 
     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) {
@@ -306,19 +306,17 @@
     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);
@@ -329,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,
@@ -380,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 {
@@ -433,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;
@@ -443,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); }
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 6f8c289..1003871 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -65,8 +65,6 @@
     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;
@@ -104,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;
@@ -118,6 +118,8 @@
 
     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,
@@ -199,7 +201,7 @@
     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
@@ -219,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);
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index 90685de..dd46bbc 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -16,17 +16,23 @@
 
 #include "CapturedTouchpadEventConverter.h"
 
+#include <optional>
 #include <sstream>
 
 #include <android-base/stringprintf.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);
 }
@@ -42,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(
@@ -53,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;
     }
 }
 
@@ -106,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);
     }
 
@@ -133,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);
     }
 }
 
@@ -164,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
@@ -176,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;
@@ -235,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
@@ -255,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});
 
@@ -287,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.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 2108488..3fc370c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -25,9 +25,6 @@
 
 namespace android {
 
-class CursorButtonAccumulator;
-class CursorScrollAccumulator;
-
 /* Keeps track of cursor movements. */
 class CursorMotionAccumulator {
 public:
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 3af1d04..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 {};
 }
@@ -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 c040a7b..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"
@@ -43,7 +45,7 @@
 
 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 b6c5c98..627df7f 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -18,6 +18,7 @@
 
 #include "InputMapper.h"
 
+#include <optional>
 #include <sstream>
 
 #include <ftl/enum.h>
@@ -116,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";
@@ -138,4 +140,7 @@
     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 2c51448..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"
@@ -120,16 +122,19 @@
     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 41e018d..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)});
             }
         }
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 8eb6730..38dcd65 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -493,12 +493,6 @@
 void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
     InputReaderContext& context = *getContext();
     context.setLastKeyDownTimestamp(downTime);
-    // 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.setPreventingTouchpadTaps(true);
-    }
 }
 
 uint32_t KeyboardInputMapper::getEventSource() const {
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 1986fe2..fd8224a 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -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/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 27ff52f..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;
@@ -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.
-        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
 
         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 14c540b..7e80415 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -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 d7f2993..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});
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
index 140bb0c..cef1837 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
@@ -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/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index a383490..5c90cbb 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -149,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) {
@@ -270,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",
@@ -356,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(
@@ -520,7 +531,7 @@
 }
 
 bool TouchInputMapper::hasExternalStylus() const {
-    return mExternalStylusConnected;
+    return mExternalStylusPresence != ExternalStylusPresence::NONE;
 }
 
 /**
@@ -600,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;
     }
@@ -618,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{
@@ -641,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) {
@@ -704,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;
         }
     }
 
@@ -725,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{
@@ -766,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;
                 }
@@ -795,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,
         };
     }
@@ -931,7 +943,7 @@
             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 {
         ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
@@ -943,12 +955,7 @@
     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.",
@@ -981,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;
@@ -1024,7 +1032,6 @@
     }
 
     // If moving between pointer modes, need to reset some state.
-    bool deviceModeChanged = mDeviceMode != oldDeviceMode;
     if (deviceModeChanged) {
         mOrientedRanges.clear();
     }
@@ -1237,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;
         }
@@ -1246,7 +1253,7 @@
     }
 
     // Pressure
-    if (mRawPointerAxes.pressure.valid) {
+    if (mRawPointerAxes.pressure) {
         if (mCalibration.pressureCalibration == Calibration::PressureCalibration::DEFAULT) {
             mCalibration.pressureCalibration = Calibration::PressureCalibration::PHYSICAL;
         }
@@ -1255,7 +1262,7 @@
     }
 
     // Orientation
-    if (mRawPointerAxes.orientation.valid) {
+    if (mRawPointerAxes.orientation) {
         if (mCalibration.orientationCalibration == Calibration::OrientationCalibration::DEFAULT) {
             mCalibration.orientationCalibration = Calibration::OrientationCalibration::INTERPOLATED;
         }
@@ -1264,7 +1271,7 @@
     }
 
     // Distance
-    if (mRawPointerAxes.distance.valid) {
+    if (mRawPointerAxes.distance) {
         if (mCalibration.distanceCalibration == Calibration::DistanceCalibration::DEFAULT) {
             mCalibration.distanceCalibration = Calibration::DistanceCalibration::SCALED;
         }
@@ -2251,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.  "
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 30c58a5..ef0e02f 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -61,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; }
@@ -339,8 +339,8 @@
         int32_t buttonState{};
 
         // Scroll state.
-        int32_t rawVScroll{};
-        int32_t rawHScroll{};
+        float rawVScroll{};
+        float rawHScroll{};
 
         inline void clear() { *this = RawState(); }
     };
@@ -365,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
@@ -460,8 +470,6 @@
     float mTiltYCenter;
     float mTiltYScale;
 
-    bool mExternalStylusConnected;
-
     // Oriented motion ranges for input device info.
     struct OrientedRanges {
         InputDeviceInfo::MotionRange x;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 24efae8..9a36bfb 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -36,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"
@@ -240,28 +241,28 @@
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
         mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
-    RawAbsoluteAxisInfo slotAxisInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
-    if (!slotAxisInfo.valid || slotAxisInfo.maxValue < 0) {
+    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.";
-        slotAxisInfo.maxValue = 0;
+        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);
 }
 
@@ -299,12 +300,8 @@
     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",
@@ -377,6 +374,7 @@
                 .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.isEnable()) ||
@@ -426,6 +424,9 @@
     }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
+        if (mTouchpadHardwareStateNotificationsEnabled) {
+            getPolicy()->notifyTouchpadHardwareState(*state, getDeviceId());
+        }
         updatePalmDetectionMetrics();
         return sendHardwareState(rawEvent.when, rawEvent.readTime, *state);
     } else {
@@ -467,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);
 }
@@ -482,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) {
@@ -501,4 +502,8 @@
     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 8baa63e..a2c4be9 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -68,6 +68,8 @@
 
     std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties() override;
+
 private:
     void resetGestureInterpreter(nsecs_t when);
     explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
@@ -92,6 +94,7 @@
     HardwareStateConverter mStateConverter;
     GestureConverter mGestureConverter;
     CapturedTouchpadEventConverter mCapturedEventConverter;
+    HardwareProperties mHardwareProperties;
 
     bool mPointerCaptured = false;
     bool mResettingInterpreter = false;
@@ -112,6 +115,10 @@
     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/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
index f85cab2..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) {
@@ -42,11 +53,31 @@
 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 e563620..d3373cc 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
@@ -16,15 +16,12 @@
 
 #pragma once
 
-#include <stdint.h>
-
 namespace android {
 
 class InputDeviceContext;
 struct RawEvent;
 
 /* Keeps track of cursor scrolling motions. */
-
 class CursorScrollAccumulator {
 public:
     CursorScrollAccumulator();
@@ -37,19 +34,17 @@
     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 4919068..8dc6e4d 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -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/SingleTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
index 2b82ddf..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() {
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index e8e7376..da2c683 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -60,16 +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),
-        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;
@@ -337,7 +354,6 @@
         NotifyMotionArgs args =
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
                                mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
-        args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
         out.push_back(args);
     }
     float deltaX = gesture.details.scroll.dx;
@@ -352,7 +368,6 @@
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                            mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
-    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     return out;
 }
@@ -426,7 +441,6 @@
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
                            mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
-    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     mCurrentClassification = MotionClassification::NONE;
     out += enterHover(when, readTime);
@@ -623,6 +637,18 @@
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
                                                   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,
@@ -632,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,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 829fb92..c9a35c1 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -99,6 +99,7 @@
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
     const bool mEnableFlingStop;
+    const bool mEnableNoFocusChange;
 
     std::optional<ui::LogicalDisplayId> mDisplayId;
     FloatRect mBoundsInLogicalDisplay{};
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.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
index 07e62c6..148ca5a 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -26,18 +26,13 @@
 #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:
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 8b44af3..e221244 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -35,11 +35,15 @@
 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 {
@@ -119,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 {
@@ -158,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
@@ -214,6 +235,7 @@
         InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
         KeyEventAction::KeyEventAction,
     };
+    use input::KeyboardType;
     use std::sync::{Arc, RwLock};
 
     #[test]
@@ -256,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());
     }
@@ -389,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
+        }
     }
 }
 
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
index 0f18a2f..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 {
@@ -217,6 +235,7 @@
     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;
@@ -240,7 +259,7 @@
     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 = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
@@ -249,6 +268,7 @@
             test_thread.clone(),
             1, /* device_id */
             SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::NonAlphabetic,
         );
 
         let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
@@ -266,6 +286,7 @@
             test_thread.clone(),
             1, /* device_id */
             SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::NonAlphabetic,
         );
 
         let event =
@@ -275,6 +296,115 @@
     }
 
     #[test]
+    fn test_notify_key_for_tv_remote_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::NonAlphabetic,
+        );
+        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());
+
+        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,
+                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());
@@ -284,6 +414,7 @@
             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 {
@@ -335,6 +466,7 @@
             test_thread.clone(),
             1, /* device_id */
             SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::Alphabetic,
         );
         let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
         filter.notify_key(&KeyEvent {
@@ -367,6 +499,7 @@
             test_thread.clone(),
             1, /* device_id */
             SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::Alphabetic,
         );
 
         let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
@@ -388,11 +521,16 @@
         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,
         )
     }
@@ -402,11 +540,16 @@
         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,
         )
     }
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index 6c7c7fb..161a5fc 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -134,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 {
@@ -235,6 +243,7 @@
         DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
         KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
     };
+    use input::KeyboardType;
     use input::ModifierState;
     use std::sync::{Arc, RwLock};
 
@@ -496,7 +505,11 @@
             ..BASE_KEY_UP
         });
 
-        sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
+        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
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index cf0d46a..744cf4a 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -70,17 +70,21 @@
         "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",
@@ -115,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 cda067f..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>
@@ -92,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.
@@ -151,6 +122,10 @@
                 .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));
@@ -193,6 +168,7 @@
 protected:
     void SetUp() override {
         input_flags::enable_new_mouse_pointer_ballistics(false);
+        vd_flags::high_resolution_scroll(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
 };
@@ -534,8 +510,9 @@
     // need to be rotated.
     mPropertyMap.addProperty("cursor.mode", "navigation");
     mPropertyMap.addProperty("cursor.orientationAware", "1");
-    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));
@@ -551,8 +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");
-    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));
@@ -563,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);
@@ -576,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));
@@ -588,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));
@@ -642,8 +623,8 @@
     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.
-    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.
@@ -663,8 +644,8 @@
     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
     // Set up the primary display as the display on which the pointer should be shown.
     // 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
@@ -835,6 +816,72 @@
                               WithOrientation(0.0f), WithDistance(0.0f)))));
 }
 
+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.
@@ -954,8 +1001,8 @@
     mPropertyMap.addProperty("cursor.mode", "pointer");
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
     mReaderConfiguration.setDisplayViewports({primaryViewport});
-    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;
 
@@ -993,9 +1040,8 @@
     mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
 
     // 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;
 
@@ -1009,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();
@@ -1023,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.
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
index 3df05f4..db68d8a 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -54,13 +54,6 @@
     ASSERT_EQ(nullptr, mFilteredEvent);
 }
 
-void FakeInputDispatcherPolicy::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 FakeInputDispatcherPolicy::assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
     std::scoped_lock lock(mLock);
     ASSERT_TRUE(mLastNotifySwitch);
@@ -342,11 +335,6 @@
     return std::make_optional(item);
 }
 
-void FakeInputDispatcherPolicy::notifyConfigurationChanged(nsecs_t when) {
-    std::scoped_lock lock(mLock);
-    mConfigurationChangedTime = when;
-}
-
 void FakeInputDispatcherPolicy::notifyWindowUnresponsive(const sp<IBinder>& connectionToken,
                                                          std::optional<gui::Pid> pid,
                                                          const std::string&) {
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index a0f3ea9..a9e39d1 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -66,7 +66,6 @@
     void assertFilterInputEventWasCalled(const NotifyKeyArgs& args);
     void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point);
     void assertFilterInputEventWasNotCalled();
-    void assertNotifyConfigurationChangedWasCalled(nsecs_t when);
     void assertNotifySwitchWasCalled(const NotifySwitchArgs& args);
     void assertOnPointerDownEquals(const sp<IBinder>& touchedToken);
     void assertOnPointerDownWasNotCalled();
@@ -121,7 +120,6 @@
 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);
 
@@ -173,7 +171,6 @@
                                                            std::condition_variable& condition)
             REQUIRES(mLock);
 
-    void notifyConfigurationChanged(nsecs_t when) override;
     void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
                                   const std::string&) override;
     void notifyWindowResponsive(const sp<IBinder>& connectionToken,
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index d2cb0ac..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);
@@ -227,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;
@@ -236,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;
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 94f1311..3a2b4e9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -42,6 +42,7 @@
     void assertInputDevicesNotChanged();
     void assertStylusGestureNotified(int32_t deviceId);
     void assertStylusGestureNotNotified();
+    void assertTouchpadHardwareStateNotified();
 
     virtual void clearViewports();
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const;
@@ -82,10 +83,14 @@
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) 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;
@@ -101,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/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index d0998ba..887a939 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -148,12 +148,6 @@
     return mIsPointerShown;
 }
 
-std::optional<FloatRect> FakePointerController::getBounds() const {
-    if (!mEnabled) return std::nullopt;
-
-    return mHaveBounds ? std::make_optional<FloatRect>(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt;
-}
-
 void FakePointerController::move(float deltaX, float deltaY) {
     if (!mEnabled) return;
 
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 2c76c62..9b773a7 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -65,7 +65,6 @@
 
 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 {}
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index d0cd677..225ae0f 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -279,6 +279,8 @@
 }
 
 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);
@@ -300,7 +302,8 @@
     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(ui::LogicalDisplayId::DEFAULT)))));
 
@@ -312,7 +315,8 @@
                               WithGestureScrollDistance(0, 5, EPSILON),
                               WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                               WithToolType(ToolType::FINGER),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              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,
@@ -325,7 +329,8 @@
                                           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(0, 0),
@@ -845,6 +850,8 @@
 }
 
 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(ui::LogicalDisplayId::DEFAULT);
@@ -867,7 +874,8 @@
                         AllOf(WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(1.0f, EPSILON),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::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);
@@ -879,7 +887,8 @@
                               WithGesturePinchScaleFactor(0.8f, EPSILON),
                               WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
                               WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::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);
@@ -891,12 +900,14 @@
                                                   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(0, 0),
@@ -908,6 +919,8 @@
 }
 
 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(ui::LogicalDisplayId::DEFAULT);
@@ -930,7 +943,8 @@
                         AllOf(WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(1.0f, EPSILON),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
@@ -942,7 +956,8 @@
                               WithGesturePinchScaleFactor(1.1f, EPSILON),
                               WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
                               WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::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);
@@ -954,12 +969,14 @@
                                                   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(0, 0),
@@ -1055,6 +1072,8 @@
 }
 
 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(ui::LogicalDisplayId::DEFAULT);
@@ -1070,7 +1089,8 @@
                                           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(0, 0),
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/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 2056372..41131fc 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -411,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,
@@ -1581,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.
@@ -1923,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;
 
 /**
@@ -4172,6 +4309,123 @@
     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",
@@ -4376,6 +4630,202 @@
     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>();
 
@@ -4734,6 +5184,54 @@
 }
 
 /**
+ * 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_legacy) {
@@ -5283,6 +5781,7 @@
 }
 
 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",
@@ -5328,6 +5827,7 @@
  * "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",
@@ -5358,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
@@ -5365,6 +6132,7 @@
  * 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)",
@@ -8111,6 +8879,7 @@
  * 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);
@@ -8469,6 +9238,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;
@@ -8476,7 +9246,8 @@
     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();
     }
 
@@ -8625,6 +9396,24 @@
     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:
@@ -12192,6 +12981,87 @@
             << "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) {
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index ff0de83..7dff144 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -54,16 +54,15 @@
 
 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) {
@@ -87,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);
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 4271a70..fc27e4f 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -51,6 +51,8 @@
 
     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);
 
diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp
index f7e5e67..d4c5a00 100644
--- a/services/inputflinger/tests/InputProcessor_test.cpp
+++ b/services/inputflinger/tests/InputProcessor_test.cpp
@@ -63,20 +63,6 @@
     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,
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index e6367b7..17c37d5 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -24,19 +24,16 @@
 #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>
@@ -56,6 +53,7 @@
 
 using namespace ftl::flag_operators;
 using testing::AllOf;
+using testing::VariantWith;
 using std::chrono_literals::operator""ms;
 using std::chrono_literals::operator""s;
 
@@ -623,7 +621,6 @@
         if (configuration) {
             mFakeEventHub->addConfigurationMap(eventHubId, configuration);
         }
-        mFakeEventHub->finishDeviceScan();
         mReader->loopOnce();
         mReader->loopOnce();
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
@@ -757,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);
@@ -774,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);
@@ -959,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;
@@ -1073,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());
 
@@ -1103,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);
@@ -1476,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());
     }
 };
 
@@ -1498,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());
 }
 
@@ -1512,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());
@@ -1523,7 +1500,6 @@
 
     keyboard.reset();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_EQ(initialNumDevices, mFakePolicy->getInputDevices().size());
 }
 
@@ -1531,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);
@@ -1668,7 +1637,6 @@
 
         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;
@@ -1737,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;
@@ -2070,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);
 
@@ -2083,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
@@ -2141,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;
@@ -2411,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);
 
@@ -2429,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);
 
@@ -2475,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);
 
@@ -2555,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);
 
@@ -3057,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 {
@@ -3470,11 +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);
 
+    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;
@@ -4532,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() {
@@ -4570,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) {
@@ -4701,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);
@@ -6290,7 +6196,7 @@
     SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
@@ -6313,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) {
@@ -6769,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);
@@ -6885,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));
@@ -10156,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 {
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
index 2ccd93e..3cc4bdd 100644
--- a/services/inputflinger/tests/InputTracingTest.cpp
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -133,8 +133,8 @@
         mDispatcher->setFocusedWindow(request);
     }
 
-    void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
-                      Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) {
+    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();
@@ -156,7 +156,7 @@
         }
     }
 
-    void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+    void keypressAndExpect(const std::vector<sp<FakeWindowHandle>>& windows,
                            Level inboundTraceLevel, Level dispatchTraceLevel,
                            InputTraceSession& s) {
         const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 8a15d07..f41b39a 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -92,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),
@@ -132,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));
@@ -188,6 +189,7 @@
     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 {
@@ -197,6 +199,7 @@
           : 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), ());
@@ -245,8 +248,6 @@
     MOCK_METHOD(int32_t, getMetaState, (), ());
     MOCK_METHOD(void, updateMetaState, (int32_t keyCode), ());
 
-    MOCK_METHOD(void, addKeyRemapping, (int32_t fromKeyCode, int32_t toKeyCode), ());
-
     MOCK_METHOD(void, setKeyboardType, (KeyboardType keyboardType), ());
 
     MOCK_METHOD(void, bumpGeneration, (), ());
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 4d322e9..88c25d3 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -70,48 +70,8 @@
         mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
                                                          AINPUT_SOURCE_KEYBOARD);
     }
-
-    void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
-                                     const bool expectPrevent) {
-        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);
-        }
-    }
 };
 
-/**
- * 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 4fcffdd..0f92833 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -61,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;
@@ -116,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) {
@@ -167,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});
 }
 
 /**
@@ -203,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);
@@ -220,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
@@ -238,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));
@@ -255,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,
@@ -272,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);
 
@@ -301,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,
@@ -332,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});
 }
 
 /**
@@ -348,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 c57c251..9a6b266 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -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({});
@@ -210,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/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 9a5b6a7..411c7ba 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -196,7 +196,6 @@
 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)
@@ -214,9 +213,6 @@
                                    [&](const NotifyInputDevicesChangedArgs& args) {
                                        mTestListener.assertNotifyInputDevicesChangedWasCalled();
                                    },
-                                   [&](const NotifyConfigurationChangedArgs& args) {
-                                       mTestListener.assertNotifyConfigurationChangedWasCalled();
-                                   },
                                    [&](const NotifyKeyArgs& args) {
                                        mTestListener.assertNotifyKeyWasCalled();
                                    },
@@ -830,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)
@@ -846,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) {
@@ -911,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>;
 
@@ -2294,7 +2391,13 @@
     assertPointerControllerRemoved(pc);
 }
 
-class PointerVisibilityOnKeyPressTest : public PointerChoreographerTest {
+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},
@@ -2358,15 +2461,28 @@
     }
 };
 
-TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutImeConnectionDoesNotHidePointer) {
+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, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+            {/*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);
@@ -2374,16 +2490,19 @@
     ASSERT_TRUE(pc->isPointerShown());
 }
 
-TEST_F(PointerVisibilityOnKeyPressTest, AlphanumericKeystrokesWithImeConnectionHidePointer) {
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+       AlphanumericKeystrokesWithImeConnectionHidePointerAndDisablesTouchpadTap) {
+    const auto& [_, source] = GetParam();
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Mouse connected
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+            {/*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());
@@ -2394,17 +2513,19 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
-TEST_F(PointerVisibilityOnKeyPressTest, MetaKeystrokesDoNotHidePointer) {
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+       MetaKeystrokesDoNotHidePointerOrDisablesTouchpadTap) {
+    const auto& [_, source] = GetParam();
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Mouse connected
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0,
-             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+            {/*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,
@@ -2420,14 +2541,16 @@
     ASSERT_TRUE(pc->isPointerShown());
 }
 
-TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutTargetHidePointerOnlyOnFocusedDisplay) {
+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, AINPUT_SOURCE_MOUSE, DISPLAY_ID),
+             {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);
@@ -2435,6 +2558,7 @@
     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());
@@ -2446,16 +2570,19 @@
     ASSERT_TRUE(pc2->isPointerShown());
 }
 
-TEST_F(PointerVisibilityOnKeyPressTest, TestMetaKeyCombinations) {
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture, TestMetaKeyCombinations) {
+    const auto& [_, source] = GetParam();
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Mouse connected
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+            {/*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
+    // 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);
@@ -2463,6 +2590,7 @@
     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);
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 a6d9d5b..6fa3365 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -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 12fa835..ea69fff 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -103,11 +103,8 @@
         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);
@@ -175,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 853f628..bbb2fc8 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -414,20 +414,6 @@
 };
 
 /**
- * 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.
  */
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/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index 0b4ac1f..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,
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 7d26a43..5442a65 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -117,6 +117,10 @@
         return reader->getSensors(deviceId);
     }
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties(int32_t deviceId) {
+        return reader->getTouchpadHardwareProperties(deviceId);
+    }
+
     bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) {
         return reader->canDispatchToDisplay(deviceId, displayId);
     }
@@ -151,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 +169,8 @@
 
     DeviceId getLastUsedInputDeviceId() override { return reader->getLastUsedInputDeviceId(); }
 
+    void notifyMouseCursorFadedOnTyping() override { reader->notifyMouseCursorFadedOnTyping(); }
+
 private:
     std::unique_ptr<InputReaderInterface> reader;
 };
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 ff425dd..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 {
@@ -269,6 +282,9 @@
     FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     void getReaderConfiguration(InputReaderConfiguration* outConfig) 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& identifier,
             const std::optional<KeyboardLayoutInfo> layoutInfo) override {
@@ -293,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 {}
@@ -333,8 +348,8 @@
     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; };
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index c620032..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>(),
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index f4b0265..7b2596a 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -52,6 +52,7 @@
         "-Wall",
         "-Werror",
         "-Wextra",
+        "-Wthread-safety",
         "-fvisibility=hidden",
     ],
 
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/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 3446f58..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;
@@ -712,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;
@@ -751,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 31b7f88..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);
@@ -1583,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);
 
@@ -1591,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();
@@ -1729,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) {
@@ -2149,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;
 }
 
@@ -2172,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;
 }
 
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/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/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 1b6c598..c2a9880 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -47,6 +47,7 @@
         "libtimestats_deps",
         "libsurfaceflinger_common_deps",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_proto_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"SurfaceFlinger\"",
@@ -85,7 +86,7 @@
         "libui",
         "libutils",
         "libSurfaceFlingerProp",
-        "libaconfig_storage_read_api_cc"
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "iinputflinger_aidl_lib_static",
@@ -93,7 +94,6 @@
         "libcompositionengine",
         "libframetimeline",
         "libgui_aidl_static",
-        "liblayers_proto",
         "libperfetto_client_experimental",
         "librenderengine",
         "libscheduler",
@@ -187,6 +187,7 @@
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
         "HdrSdrRatioOverlay.cpp",
+        "Jank/JankTracker.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
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 7fa58df..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\"",
@@ -41,16 +42,14 @@
         "libutils",
     ],
     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",
@@ -148,6 +147,7 @@
         "tests/CompositionEngineTest.cpp",
         "tests/DisplayColorProfileTest.cpp",
         "tests/DisplayTest.cpp",
+        "tests/HwcAsyncWorkerTest.cpp",
         "tests/HwcBufferCacheTest.cpp",
         "tests/MockHWC2.cpp",
         "tests/MockHWComposer.cpp",
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/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 4c77687..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,7 +155,7 @@
     }
 
     {
-        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().
@@ -177,7 +177,7 @@
 }
 
 void CompositionEngine::preComposition(CompositionRefreshArgs& args) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     bool needsAnotherUpdate = false;
@@ -199,7 +199,7 @@
 // promises for buffer releases are fulfilled at the end of composition.
 void CompositionEngine::postComposition(CompositionRefreshArgs& args) {
     if (FlagManager::getInstance().ce_fence_promise()) {
-        ATRACE_CALL();
+        SFTRACE_CALL();
         ALOGV(__FUNCTION__);
 
         for (auto& layerFE : args.layers) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index c1617d7..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
@@ -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) {
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 5b9a102..2d8f98f 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -17,6 +17,7 @@
 #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>
@@ -31,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>
 
@@ -53,7 +53,6 @@
 #include <android-base/properties.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
-#include <utils/Trace.h>
 
 #include "TracedOrdinal.h"
 
@@ -424,7 +423,7 @@
 
 void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArgs,
                      LayerFESet& geomSnapshots) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     rebuildLayerStacks(refreshArgs, geomSnapshots);
@@ -453,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);
@@ -518,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
@@ -804,7 +803,7 @@
 }
 
 void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
@@ -831,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) {
@@ -1081,7 +1080,7 @@
 }
 
 void Output::prepareFrame() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     auto& outputState = editState();
@@ -1102,10 +1101,10 @@
 }
 
 ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) {
-    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
+    return ftl::Future<bool>(mHwComposerAsyncWorker->send([this, flushEvenWhenDisabled]() {
                presentFrameAndReleaseLayers(flushEvenWhenDisabled);
                return true;
-           })))
+           }))
             .then([](bool) { return std::monostate{}; });
 }
 
@@ -1116,7 +1115,7 @@
 }
 
 GpuCompositionResult Output::prepareFrameAsync() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
     auto& state = editState();
     const auto& previousChanges = state.previousDeviceRequestedChanges;
@@ -1146,7 +1145,7 @@
     state.strategyPrediction = predictionSucceeded ? CompositionStrategyPredictionState::SUCCESS
                                                    : CompositionStrategyPredictionState::FAIL;
     if (!predictionSucceeded) {
-        ATRACE_NAME("CompositionStrategyPredictionMiss");
+        SFTRACE_NAME("CompositionStrategyPredictionMiss");
         resetCompositionStrategy();
         if (chooseCompositionSuccess) {
             applyCompositionStrategy(changes);
@@ -1155,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;
@@ -1187,7 +1186,7 @@
 }
 
 void Output::finishFrame(GpuCompositionResult&& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
     const auto& outputState = getState();
     if (!outputState.isEnabled) {
@@ -1276,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();
@@ -1317,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);
     }
@@ -1570,7 +1569,7 @@
 }
 
 void Output::presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) {
-    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
+    SFTRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
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 ea9442d..409a206 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -21,6 +21,7 @@
 
 #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>
@@ -28,7 +29,6 @@
 #include <renderengine/RenderEngine.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrRenderTypeUtils.h>
-#include <utils/Trace.h>
 
 namespace android::compositionengine::impl::planner {
 
@@ -160,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();
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 4bafed2..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,8 +430,8 @@
         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;
             }
         }
@@ -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/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/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 629d9f2..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,
@@ -151,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/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
index a6a9bec..f8b6c6e 100644
--- a/services/surfaceflinger/Display/DisplayModeController.cpp
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -22,7 +22,9 @@
 #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>
@@ -83,7 +85,7 @@
             FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get();
 
     {
-        ATRACE_NAME(displayPtr->concatId(__func__).c_str());
+        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);
@@ -204,7 +206,7 @@
         return false;
     }
 
-    ATRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+    SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
     return true;
 }
 
@@ -227,8 +229,8 @@
                                                 Fps vsyncRate, Fps renderFps) {
     const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
 
-    ATRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
-    ATRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());
+    SFTRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
+    SFTRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());
 
     displayPtr->selectorPtr->setActiveMode(modeId, renderFps);
 
@@ -237,4 +239,66 @@
     }
 }
 
+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
index 258b04b..9ec603d 100644
--- a/services/surfaceflinger/Display/DisplayModeController.h
+++ b/services/surfaceflinger/Display/DisplayModeController.h
@@ -97,6 +97,17 @@
     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>
@@ -121,6 +132,8 @@
 
         DisplayModeRequestOpt pendingModeOpt GUARDED_BY(kMainThreadContext);
         bool isModeSetPending GUARDED_BY(kMainThreadContext) = false;
+
+        bool isKernelIdleTimerEnabled GUARDED_BY(kMainThreadContext) = false;
     };
 
     using DisplayPtr = std::unique_ptr<Display>;
@@ -128,6 +141,10 @@
     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;
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 27ea4a9..402a3d2 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,6 +24,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -200,19 +201,6 @@
     return mPowerMode != hal::PowerMode::OFF;
 }
 
-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;
 }
@@ -398,7 +386,7 @@
 }
 
 void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mHdrSdrRatio = currentHdrSdrRatio;
     if (mHdrSdrRatioOverlay) {
         mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
@@ -440,7 +428,7 @@
 }
 
 void DisplayDevice::updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mRefreshRateOverlay) {
         if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
             if (mRefreshRateSelector->isVrrDevice() && !mRefreshRateOverlay->isSetByHwc()) {
@@ -467,6 +455,12 @@
     return false;
 }
 
+void DisplayDevice::onVrrIdle(bool idle) {
+    if (mRefreshRateOverlay) {
+        mRefreshRateOverlay->onVrrIdle(idle);
+    }
+}
+
 void DisplayDevice::animateOverlay() {
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->animate();
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 3cc8cf5..3e3f558 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -196,14 +196,13 @@
     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
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 3d285a8..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,6 +76,7 @@
 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::DisplayConfiguration;
 using namespace std::string_literals;
 
 namespace android {
@@ -178,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;
@@ -223,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) {
@@ -236,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);
 }
 
@@ -277,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;
@@ -295,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) {
@@ -329,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);
     }
@@ -428,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,
@@ -455,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);
 
@@ -493,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;
@@ -534,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),
@@ -568,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);
 
@@ -584,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);
     }
 
@@ -897,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());
@@ -926,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);
@@ -1057,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);
             }
@@ -1099,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;
 }
@@ -1149,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) {
@@ -1168,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 9368b7b..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;
@@ -309,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,
@@ -343,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;
@@ -472,6 +480,12 @@
     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;
@@ -520,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 6c1a813..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>
 
@@ -74,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);
     }
 }
 
@@ -210,8 +210,8 @@
         ALOGV("Power hint session is not enabled, skip sending session hint");
         return;
     }
-    ATRACE_CALL();
-    if (sTraceHintSessionData) ATRACE_INT("Session hint", static_cast<int>(hint));
+    SFTRACE_CALL();
+    if (sTraceHintSessionData) SFTRACE_INT("Session hint", static_cast<int>(hint));
     {
         std::scoped_lock lock(mHintSessionMutex);
         if (!ensurePowerHintSessionRunning()) {
@@ -295,10 +295,10 @@
         ALOGV("Power hint session is not enabled, skipping target update");
         return;
     }
-    ATRACE_CALL();
+    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::scoped_lock lock(mHintSessionMutex);
         if (!ensurePowerHintSessionRunning()) {
@@ -324,7 +324,7 @@
         ALOGV("Actual work duration power hint cannot be sent, skipping");
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::optional<WorkDuration> actualDuration = estimateWorkDuration();
     if (!actualDuration.has_value() || actualDuration->durationNanos < 0) {
         ALOGV("Failed to send actual work duration, skipping");
@@ -332,16 +332,16 @@
     }
     actualDuration->durationNanos += sTargetSafetyMargin.ns();
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", actualDuration->durationNanos);
-        ATRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
-        ATRACE_INT64("Reported duration", actualDuration->durationNanos);
+        SFTRACE_INT64("Measured duration", actualDuration->durationNanos);
+        SFTRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
+        SFTRACE_INT64("Reported duration", actualDuration->durationNanos);
         if (supportsGpuReporting()) {
-            ATRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
-            ATRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
+            SFTRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
+            SFTRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
         }
-        ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
-        ATRACE_INT64("Reported target error term",
-                     actualDuration->durationNanos - mLastTargetDurationSent.ns());
+        SFTRACE_INT64("Reported target", mLastTargetDurationSent.ns());
+        SFTRACE_INT64("Reported target error term",
+                      actualDuration->durationNanos - mLastTargetDurationSent.ns());
     }
 
     ALOGV("Sending actual work duration of: %" PRId64 " with cpu: %" PRId64 " and gpu: %" PRId64
@@ -664,9 +664,9 @@
             .gpuDurationNanos = supportsGpuReporting() ? estimatedGpuDuration.ns() : 0,
     };
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Idle duration", idleDuration.ns());
-        ATRACE_INT64("Total duration", totalDuration.ns());
-        ATRACE_INT64("Flinger duration", flingerDuration.ns());
+        SFTRACE_INT64("Idle duration", idleDuration.ns());
+        SFTRACE_INT64("Total duration", totalDuration.ns());
+        SFTRACE_INT64("Flinger duration", flingerDuration.ns());
     }
     return std::make_optional(duration);
 }
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index bc4a41b..1076b2b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -300,7 +300,7 @@
     bool mSessionConfigSupported = true;
     bool mFirstConfigSupportCheck = true;
 
-    // Whether we should emit ATRACE_INT data for hint sessions
+    // Whether we should emit SFTRACE_INT data for hint sessions
     static const bool sTraceHintSessionData;
 
     // Default target duration for the hint session
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/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 2596a25..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) {
@@ -685,6 +703,30 @@
         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);
     }
 }
 
@@ -696,15 +738,24 @@
     classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, nullptr);
 }
 
-void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+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();
@@ -718,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();
@@ -782,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.
@@ -812,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());
@@ -840,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),
@@ -866,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),
@@ -902,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);
@@ -918,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);
@@ -928,7 +998,7 @@
 }
 
 void FrameTimeline::onCommitNotComposited() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->onCommitNotComposited();
     mCurrentDisplayFrame.reset();
@@ -1124,16 +1194,23 @@
     }
 }
 
-void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid,
-                                                   nsecs_t monoBootOffset) const {
+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();
@@ -1144,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;
@@ -1175,7 +1255,7 @@
                     (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;
@@ -1185,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));
@@ -1208,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();
@@ -1250,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
@@ -1281,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;
 }
@@ -1384,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());
     }
 
@@ -1401,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));
@@ -1507,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 94cfcb4..cffb61e 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -214,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;
@@ -234,8 +235,10 @@
             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* outDeadlineDelta) REQUIRES(mMutex);
 
@@ -367,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;
     };
 
     /*
@@ -390,7 +399,8 @@
         // 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);
@@ -424,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);
 
@@ -471,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; }
@@ -516,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/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 39a6b77..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"
@@ -413,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
@@ -426,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);
@@ -485,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};
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index d023f9e..47d0041 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -211,8 +211,11 @@
     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*);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 70e3c64..e5f6b7b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -322,6 +322,10 @@
             << touchableRegion.bottom << "," << touchableRegion.right << "}"
             << "}";
     }
+
+    if (obj.edgeExtensionEffect.hasEffect()) {
+        out << obj.edgeExtensionEffect;
+    }
     return out;
 }
 
@@ -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/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ca53a0d..ee605b7 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -23,8 +23,8 @@
 #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>
 
@@ -279,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();
@@ -317,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;
@@ -352,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;
@@ -387,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) {
@@ -421,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;
@@ -548,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,
@@ -555,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;
@@ -662,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) &&
@@ -674,7 +672,7 @@
     }
 
     using FrameRateCompatibility = scheduler::FrameRateCompatibility;
-    if (snapshot.frameRate.isValid()) {
+    if (snapshot.inheritedFrameRate.isValid() || *outChildHasValidFrameRate) {
         // we already have a valid framerate.
         return;
     }
@@ -691,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;
     }
 }
@@ -762,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;
     }
 
@@ -791,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;
@@ -801,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 ||
@@ -886,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)) {
@@ -904,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;
@@ -960,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,
@@ -999,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),
@@ -1084,7 +1144,7 @@
     }
 
     updateVisibility(snapshot, snapshot.isVisible);
-    if (!needsInputInfo(snapshot, requested)) {
+    if (!requested.needsInputInfo()) {
         return;
     }
 
@@ -1094,7 +1154,7 @@
     bool noValidDisplay = !displayInfoOpt.has_value();
     auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
 
-    if (!requested.windowInfoHandle) {
+    if (!requested.hasInputInfo()) {
         snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
     }
     fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
@@ -1178,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 3e8d740..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;
     }
@@ -328,6 +330,7 @@
                 changes |= RequestedLayerState::Changes::GameMode;
             }
         }
+        changes |= RequestedLayerState::Changes::Metadata;
     }
     if (clientState.what & layer_state_t::eFrameRateChanged) {
         const auto compatibility =
@@ -552,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;
 }
@@ -580,8 +601,8 @@
 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;
     }
 
@@ -593,8 +614,8 @@
                      ? 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;
     }
 
@@ -608,15 +629,16 @@
             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::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__,
-                              changedFlags & 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 48b9640..1d96dff 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -87,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 c39b757..dcb0812 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -27,6 +27,7 @@
 #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,7 +40,6 @@
 #include <ftl/fake_guard.h>
 #include <gui/BufferItem.h>
 #include <gui/Surface.h>
-#include <gui/TraceUtils.h>
 #include <math.h>
 #include <private/android_filesystem_config.h>
 #include <renderengine/RenderEngine.h>
@@ -58,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,7 +70,6 @@
 #include "FrontEnd/LayerHandle.h"
 #include "Layer.h"
 #include "LayerProtoHelper.h"
-#include "MutexUtils.h"
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
 #include "TransactionCallbackInvoker.h"
@@ -90,18 +86,6 @@
 
 const ui::Transform kIdentityTransform;
 
-ui::LogicalDisplayId toLogicalDisplayId(const ui::LayerStack& layerStack) {
-    return ui::LogicalDisplayId{static_cast<int32_t>(layerStack.id)};
-}
-
-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;
@@ -150,24 +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),
-        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName, this)) {
+                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;
@@ -180,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());
 
@@ -214,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() {
@@ -232,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,
@@ -251,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*/);
@@ -262,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) {
@@ -349,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,
@@ -490,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);
@@ -552,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();
 }
@@ -754,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.
@@ -853,504 +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::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
@@ -1366,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;
@@ -1392,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(
@@ -1422,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);
@@ -1436,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);
@@ -1453,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;
     }
@@ -1470,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);
@@ -1479,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) {
@@ -1519,48 +535,6 @@
     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::updateTransformHint(ui::Transform::RotationFlags transformHint) {
-    if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) {
-        transformHint = ui::Transform::ROT_0;
-    }
-
-    setTransformHintLegacy(transformHint);
-}
-
 // ----------------------------------------------------------------------------
 // debugging
 // ----------------------------------------------------------------------------
@@ -1580,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);
@@ -1690,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.
@@ -2206,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 = toLogicalDisplayId(getLayerStack());
-    }
-
-    const ui::Transform& displayTransform =
-            displayArgs.transform != nullptr ? *displayArgs.transform : kIdentityTransform;
-
-    WindowInfo info = mDrawingState.inputInfo;
-    info.id = sequence;
-    info.displayId = toLogicalDisplayId(getLayerStack());
-
-    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) {
@@ -2598,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) {
@@ -2618,215 +668,27 @@
     return outputLayer ? outputLayer->getState().visibleRegion : Region();
 }
 
-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();
-            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);
+    }
 }
 
 sp<CallbackHandle> Layer::findCallbackHandle() {
@@ -2942,33 +804,15 @@
     }
 }
 
-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();
-    }
-    mPendingJankClassifications.emplace_back(surfaceFrame);
-}
-
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
+        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) {
@@ -2978,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;
 }
@@ -3004,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 ||
@@ -3151,14 +883,14 @@
 
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
-                      bool isAutoTimestamp, 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();
@@ -3172,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);
         }
     }
 
@@ -3221,9 +952,9 @@
 
     const int32_t layerId = getSequence();
     mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(),
-                                      mOwnerUid, postTime, getGameMode());
+                                      mOwnerUid, postTime, gameMode);
 
-    setFrameTimelineVsyncForBufferTransaction(info, postTime);
+    setFrameTimelineVsyncForBufferTransaction(info, postTime, gameMode);
 
     if (bufferData.dequeueTime > 0) {
         const uint64_t bufferId = mDrawingState.buffer->getId();
@@ -3253,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;
         }
 
@@ -3265,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;
@@ -3301,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,
@@ -3320,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;
 }
@@ -3331,7 +1061,6 @@
         return false;
     mDrawingState.currentHdrSdrRatio = currentBufferRatio;
     mDrawingState.desiredHdrSdrRatio = desiredRatio;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
@@ -3339,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) {
@@ -3388,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)) {
@@ -3451,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;
@@ -3487,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;
@@ -3516,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());
 
@@ -3593,8 +1261,6 @@
     mFlinger->getTransactionCallbackInvoker()
             .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles);
     mDrawingState.callbackHandles = remainingHandles;
-
-    mDrawingStateModified = false;
 }
 
 void Layer::gatherBufferInfo() {
@@ -3618,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);
@@ -3629,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));
             }
@@ -3666,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;
 }
 
@@ -3685,308 +1346,13 @@
     }
 }
 
-sp<Layer> Layer::createClone() {
-    surfaceflinger::LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0,
-                                           LayerMetadata());
-    sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
-    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::eReparent |
-            (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
-                     ? 0
-                     : layer_state_t::eAutoRefreshChanged);
-
-    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.trustedOverlay == gui::TrustedOverlay::ENABLED)) {
-            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, this);
-    result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
-    return result;
+    SFTRACE_INT(mBlastTransactionName.c_str(), pendingBuffers);
 }
 
 sp<LayerFE> Layer::getCompositionEngineLayerFE(
@@ -4001,59 +1367,11 @@
     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;
@@ -4095,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,
@@ -4116,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,
@@ -4132,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);
 
@@ -4154,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;
     }
@@ -4162,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();
@@ -4187,7 +1500,6 @@
 
     if ((mBufferInfo.mCrop != oldBufferInfo.mCrop) ||
         (mBufferInfo.mTransform != oldBufferInfo.mTransform) ||
-        (mBufferInfo.mScaleMode != oldBufferInfo.mScaleMode) ||
         (mBufferInfo.mTransformToDisplayInverse != oldBufferInfo.mTransformToDisplayInverse)) {
         recomputeVisibleRegions = true;
     }
@@ -4200,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
@@ -4299,123 +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;
-    }
-}
-
-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();
@@ -4439,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 b9fcd5c..9bc557e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -43,9 +43,7 @@
 #include <scheduler/Fps.h>
 #include <scheduler/Seamlessness.h>
 
-#include <chrono>
 #include <cstdint>
-#include <list>
 #include <optional>
 #include <vector>
 
@@ -56,7 +54,6 @@
 #include "LayerVector.h"
 #include "Scheduler/LayerInfo.h"
 #include "SurfaceFlinger.h"
-#include "Tracing/LayerTracing.h"
 #include "TransactionCallbackInvoker.h"
 
 using namespace android::surfaceflinger;
@@ -91,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;
 
@@ -154,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;
 
@@ -219,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;
     };
@@ -244,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();
-
-    // 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*/,
-                   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*/);
@@ -450,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
@@ -497,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;
 
@@ -534,25 +243,18 @@
         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,
@@ -573,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,
@@ -601,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.
@@ -622,164 +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);
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
-    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.
@@ -788,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);
@@ -805,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
@@ -870,61 +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 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;
@@ -964,7 +416,6 @@
     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:
@@ -976,63 +427,16 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    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;
@@ -1042,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;
 
@@ -1097,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:
@@ -1110,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
@@ -1176,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));
     }
@@ -1193,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.
@@ -1241,22 +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>&);
-
-    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;
     std::optional<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
@@ -1268,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 c2251a8..b05f0ee 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -19,11 +19,10 @@
 #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"
@@ -122,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);
@@ -174,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;
 
@@ -214,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.
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 496033b..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) {
@@ -445,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(); });
     }
 
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 f323ce7..bfe6d2a 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -27,10 +27,9 @@
 
 LayerRenderArea::LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot,
                                  const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                                 bool allowSecureLayers, const ui::Transform& layerTransform,
-                                 const Rect& layerBufferSize, bool hintForSeamlessTransition)
-      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition,
-                   allowSecureLayers),
+                                 const ui::Transform& layerTransform, const Rect& layerBufferSize,
+                                 ftl::Flags<RenderArea::Options> options)
+      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, options),
         mLayer(std::move(layer)),
         mLayerSnapshot(std::move(layerSnapshot)),
         mLayerBufferSize(layerBufferSize),
@@ -42,7 +41,7 @@
 }
 
 bool LayerRenderArea::isSecure() const {
-    return mAllowSecureLayers;
+    return mOptions.test(Options::CAPTURE_SECURE_LAYERS);
 }
 
 sp<const DisplayDevice> LayerRenderArea::getDisplayDevice() const {
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index a12bfca..f72c7c7 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -33,9 +33,9 @@
 class LayerRenderArea : public RenderArea {
 public:
     LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop,
-                    ui::Size reqSize, ui::Dataspace reqDataSpace, bool allowSecureLayers,
+                    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;
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..fa0ecee 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -5,6 +5,8 @@
 domlaskowski@google.com
 jreck@google.com
 lpy@google.com
+mattbuckley@google.com
+melodymhsu@google.com
 pdwilliams@google.com
 racarr@google.com
 ramindani@google.com
@@ -12,3 +14,4 @@
 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 9527a99..35f12a0 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -28,10 +28,11 @@
 
 namespace android {
 
-auto RefreshRateOverlay::draw(int refreshRate, 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(refreshRate, 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 refreshRate, Fps renderFps) -> const Buffers& {
+auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps, bool idle)
+        -> const Buffers& {
     static const Buffers kNoBuffers;
     if (!mSurfaceControl) return kNoBuffers;
 
@@ -197,8 +215,8 @@
 
     createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
-    BufferCache::const_iterator it =
-            mBufferCache.find({refreshRate.getIntValue(), renderFps.getIntValue(), transformHint});
+    BufferCache::const_iterator it = mBufferCache.find(
+            {refreshRate.getIntValue(), renderFps.getIntValue(), transformHint, idle});
     if (it == mBufferCache.end()) {
         const int maxFps = mFpsRange.max.getIntValue();
 
@@ -222,10 +240,10 @@
 
         const SkColor color = colorBase.toSkColor();
 
-        auto buffers = draw(refreshIntFps, renderIntFps, color, transformHint, mFeatures);
+        auto buffers = draw(refreshIntFps, renderIntFps, idle, color, transformHint, mFeatures);
         it = mBufferCache
-                     .try_emplace({refreshIntFps, renderIntFps, transformHint}, std::move(buffers))
-                     .first;
+                     .try_emplace({refreshIntFps, renderIntFps, transformHint, idle},
+                     std::move(buffers)).first;
     }
 
     return it->second;
@@ -257,7 +275,15 @@
 void RefreshRateOverlay::changeRefreshRate(Fps refreshRate, Fps renderFps) {
     mRefreshRate = refreshRate;
     mRenderFps = renderFps;
-    const auto buffer = getOrCreateBuffers(refreshRate, 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();
 }
 
@@ -265,7 +291,7 @@
     if (mFeatures.test(Features::RenderRate) && mRefreshRate &&
         FlagManager::getInstance().misc1()) {
         mRenderFps = renderFps;
-        const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps)[mFrame];
+        const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps, mIsVrrIdle)[mFrame];
         createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
     }
 }
@@ -273,7 +299,7 @@
 void RefreshRateOverlay::animate() {
     if (!mFeatures.test(Features::Spinner) || !mRefreshRate) return;
 
-    const auto& buffers = getOrCreateBuffers(*mRefreshRate, *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 b2896f0..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,11 +66,12 @@
 
     using Buffers = std::vector<sp<GraphicBuffer>>;
 
-    static Buffers draw(int refreshRate, 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;
 
@@ -77,10 +79,11 @@
         int refreshRate;
         int renderFps;
         ui::Transform::RotationFlags flags;
+        bool idle;
 
         bool operator==(Key other) const {
             return refreshRate == other.refreshRate && renderFps == other.renderFps &&
-                    flags == other.flags;
+                    flags == other.flags && idle == other.idle;
         }
     };
 
@@ -89,6 +92,7 @@
 
     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 2ad70bb..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>
 
@@ -148,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();
     }
@@ -166,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;
     }
@@ -175,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;
@@ -247,7 +247,7 @@
 }
 
 void RegionSamplingThread::captureSample() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mSamplingMutex);
 
     if (mDescriptors.empty()) {
@@ -277,7 +277,6 @@
     }
 
     const Rect sampledBounds = sampleRegion.bounds();
-    constexpr bool kHintForSeamlessTransition = false;
 
     std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners;
 
@@ -347,29 +346,28 @@
     constexpr bool kRegionSampling = true;
     constexpr bool kGrayscale = false;
     constexpr bool kIsProtected = false;
+    constexpr bool kAttachGainmap = false;
 
     SurfaceFlinger::RenderAreaBuilderVariant
             renderAreaBuilder(std::in_place_type<DisplayRenderAreaBuilder>, sampledBounds,
-                              sampledBounds.getSize(), ui::Dataspace::V0_SRGB,
-                              kHintForSeamlessTransition, true /* captureSecureLayers */,
-                              displayWeak);
+                              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.getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder,
-                                                                   getLayerSnapshotsFn, layerFEs);
-        fenceResult =
-                mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale,
-                                           kIsProtected, nullptr, displayState, layerFEs)
-                        .get();
+        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, nullptr)
-                        .get();
+        fenceResult = mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn,
+                                                       buffer, kRegionSampling, kGrayscale,
+                                                       kIsProtected, kAttachGainmap, nullptr)
+                              .get();
     }
     if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
@@ -396,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 e8d20af..aa66ccf 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -21,31 +21,23 @@
 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;
 
@@ -90,16 +82,17 @@
 
     // 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
index a25c6e0..599fa7e 100644
--- a/services/surfaceflinger/RenderAreaBuilder.h
+++ b/services/surfaceflinger/RenderAreaBuilder.h
@@ -36,50 +36,34 @@
     // Composition data space of the render area
     ui::Dataspace reqDataSpace;
 
-    // If true, the secure layer would be blacked out or skipped
-    // when rendered to an insecure render area
-    bool allowSecureLayers;
-
-    // If true, the render result may be used for system animations
-    // that must preserve the exact colors of the display
-    bool hintForSeamlessTransition;
-
+    ftl::Flags<RenderArea::Options> options;
     virtual std::unique_ptr<RenderArea> build() const = 0;
 
     RenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                      bool allowSecureLayers, bool hintForSeamlessTransition)
-          : crop(crop),
-            reqSize(reqSize),
-            reqDataSpace(reqDataSpace),
-            allowSecureLayers(allowSecureLayers),
-            hintForSeamlessTransition(hintForSeamlessTransition) {}
+                      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,
-                             bool allowSecureLayers, bool hintForSeamlessTransition,
-                             wp<const DisplayDevice> displayWeak)
-          : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers,
-                              hintForSeamlessTransition),
-            displayWeak(displayWeak) {}
+                             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,
-                                         hintForSeamlessTransition, allowSecureLayers);
+        return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace, options);
     }
 };
 
 struct LayerRenderAreaBuilder : RenderAreaBuilder {
-    LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                           bool allowSecureLayers, bool hintForSeamlessTransition, sp<Layer> layer,
-                           bool childrenOnly)
-          : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers,
-                              hintForSeamlessTransition),
+    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) {}
 
@@ -110,8 +94,8 @@
 
     std::unique_ptr<RenderArea> build() const override {
         return std::make_unique<LayerRenderArea>(layer, std::move(layerSnapshot), crop, reqSize,
-                                                 reqDataSpace, allowSecureLayers, layerTransform,
-                                                 layerBufferSize, hintForSeamlessTransition);
+                                                 reqDataSpace, layerTransform, layerBufferSize,
+                                                 options);
     }
 };
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 6b65449..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,14 +225,14 @@
 }
 
 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),
                                                   systemTime());
@@ -321,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(
@@ -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 f772126..bbe4f9d 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -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 43cdb5e..d02d149 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -28,11 +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(PhysicalDisplayId pacesetterDisplayId) = 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 a819b79..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>
 
@@ -72,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);
@@ -109,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
@@ -190,7 +190,7 @@
 }
 
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     Summary summary;
 
     std::lock_guard lock(mLock);
@@ -204,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()) {
@@ -222,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});
@@ -238,7 +238,7 @@
 }
 
 void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
     // iterate over inactive map
@@ -310,7 +310,7 @@
 
                 if (gameModeFrameRateOverride.isValid()) {
                     info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
-                    ATRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
+                    SFTRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameModeFrameRateOverride.getIntValue());
@@ -326,19 +326,19 @@
                 } 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)) {
-                        ATRACE_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());
+                        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();
                 }
@@ -349,12 +349,12 @@
                                         frameRate.vote.seamlessness, frameRate.category});
                 } else {
                     if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
-                        ATRACE_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());
+                        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();
                 }
@@ -421,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 c09f148..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; }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 632f42a..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
@@ -259,7 +259,7 @@
     }
 
     if (smallDirtyCount > 0) {
-        ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
+        SFTRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
     }
 
     if (numDeltas == 0) {
@@ -272,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");
@@ -307,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) {
@@ -315,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());
@@ -326,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});
@@ -336,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()});
@@ -345,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;
@@ -354,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;
@@ -365,14 +365,14 @@
     }
 
     if (frequent.clearHistory) {
-        ATRACE_FORMAT_INSTANT("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;
@@ -380,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;
@@ -452,7 +452,7 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
     }
 
     return selectRefreshRate(selector);
@@ -486,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();
@@ -595,6 +595,11 @@
         return true;
     }
 
+    if (category == FrameRateCategory::NoPreference && vote.rate.isValid() &&
+        vote.type == FrameRateCompatibility::ExactOrMultiple) {
+        return true;
+    }
+
     return false;
 }
 
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/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 846727b..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"
 
@@ -494,7 +493,7 @@
                                                     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;
@@ -508,8 +507,8 @@
                                             });
 
         if (!ranking.empty()) {
-            ATRACE_FORMAT_INSTANT("%s (Follower display)",
-                                  to_string(ranking.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (Follower display)",
+                                   to_string(ranking.front().frameRateMode.fps).c_str());
 
             return {ranking, kNoSignals, pacesetterFps};
         }
@@ -521,8 +520,8 @@
     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}};
     }
 
@@ -608,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}};
     }
 
@@ -620,26 +619,27 @@
         !(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");
-        const auto ascendingWithPreferred =
-                rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
-        ATRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
-                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
-        return {ascendingWithPreferred, kNoSignals};
+        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;
@@ -653,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};
     }
 
@@ -841,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};
         }
     }
@@ -887,10 +888,10 @@
         const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
         using fps_approx_ops::operator<;
 
-        if (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());
+        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}};
         }
     }
@@ -901,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());
+    SFTRACE_FORMAT_INSTANT("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
 
@@ -949,7 +950,7 @@
                                                 Fps displayRefreshRate,
                                                 GlobalSignals globalSignals) const
         -> UidToFrameRateOverride {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
         return {};
     }
@@ -1064,12 +1065,12 @@
                                       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);
-        if (ATRACE_ENABLED() && FlagManager::getInstance().trace_frame_rate_override()) {
+        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;
-            ATRACE_INT(ss.str().c_str(), overrideFps.getIntValue());
+            SFTRACE_INT(ss.str().c_str(), overrideFps.getIntValue());
         }
         frameRateOverrides.emplace(uid, overrideFps);
     }
@@ -1394,13 +1395,14 @@
         const auto& idleScreenConfigOpt = getCurrentPolicyLocked()->idleScreenConfigOpt;
         if (idleScreenConfigOpt != oldPolicy.idleScreenConfigOpt) {
             if (!idleScreenConfigOpt.has_value()) {
-                // fallback to legacy timer if existed, otherwise pause the old timer
-                LOG_ALWAYS_FATAL_IF(!mIdleTimer);
-                if (mConfig.legacyIdleTimerTimeout > 0ms) {
-                    mIdleTimer->setInterval(mConfig.legacyIdleTimerTimeout);
-                    mIdleTimer->resume();
-                } else {
-                    mIdleTimer->pause();
+                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
@@ -1501,7 +1503,7 @@
             return str;
         };
         ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(),
-              mIsVrrDevice);
+              mIsVrrDevice.load());
 
         return frameRateModes;
     };
@@ -1511,7 +1513,6 @@
 }
 
 bool RefreshRateSelector::isVrrDevice() const {
-    std::lock_guard lock(mLock);
     return mIsVrrDevice;
 }
 
@@ -1641,7 +1642,7 @@
         case FrameRateCategory::Normal:
             return FpsRange{60_Hz, 120_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 120_Hz};
+            return FpsRange{48_Hz, 120_Hz};
         case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 4f491d9..a398c01 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -210,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;
@@ -321,7 +323,10 @@
     RefreshRateSelector(const RefreshRateSelector&) = delete;
     RefreshRateSelector& operator=(const RefreshRateSelector&) = delete;
 
-    const DisplayModes& displayModes() const { return mDisplayModes; }
+    DisplayModes displayModes() const {
+        std::lock_guard lock(mLock);
+        return mDisplayModes;
+    }
 
     // Returns whether switching modes (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
@@ -383,6 +388,7 @@
 
         Callbacks platform;
         Callbacks kernel;
+        Callbacks vrr;
     };
 
     void setIdleTimerCallbacks(IdleTimerCallbacks callbacks) EXCLUDES(mIdleTimerCallbacksMutex) {
@@ -501,6 +507,9 @@
     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;
     }
@@ -536,7 +545,7 @@
     std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
 
     // Caches whether the device is VRR-compatible based on the active display mode.
-    bool mIsVrrDevice GUARDED_BY(mLock) = false;
+    std::atomic_bool mIsVrrDevice = false;
 
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 60681a2..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,7 +238,7 @@
             if (FlagManager::getInstance().vrr_config()) {
                 compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
             }
-            mSchedulerCallback.onCommitNotComposited(pacesetterPtr->displayId);
+            mSchedulerCallback.onCommitNotComposited();
             return;
         }
     }
@@ -245,8 +262,8 @@
         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;
     }
@@ -277,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);
 }
 
@@ -342,10 +359,8 @@
 
     if (cycle == Cycle::Render) {
         mRenderEventThread = std::move(eventThread);
-        mRenderEventConnection = mRenderEventThread->createEventConnection();
     } else {
         mLastCompositeEventThread = std::move(eventThread);
-        mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
     }
 }
 
@@ -390,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,
@@ -405,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);
     }
 }
 
@@ -463,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);
@@ -505,7 +528,7 @@
 }
 
 void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
@@ -539,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();
@@ -626,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
@@ -646,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) {
@@ -689,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));
@@ -774,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
@@ -805,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) {
@@ -817,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 {
@@ -858,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();
     }
 }
 
@@ -894,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) {
@@ -912,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;
 
@@ -960,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.
@@ -985,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();
@@ -1067,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;
 
@@ -1110,33 +1144,40 @@
         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();
+    SFTRACE_CALL();
 
     DisplayModeChoiceMap modeChoices;
     const auto globalSignals = makeGlobalSignals();
@@ -1230,6 +1271,8 @@
     } else {
         mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
     }
+
+    onFrameRateOverridesChanged();
 }
 
 void Scheduler::setGameDefaultFrameRateForUid(FrameRateOverride frameRateOverride) {
@@ -1248,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 ccaa05f..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,8 +183,7 @@
         }
     }
 
-    void updatePhaseConfiguration(Fps);
-    void resetPhaseConfiguration(Fps) REQUIRES(kMainThreadContext);
+    void updatePhaseConfiguration(PhysicalDisplayId, Fps);
 
     const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; }
 
@@ -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) {
@@ -370,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
@@ -381,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;
 
@@ -433,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&,
@@ -440,7 +453,7 @@
     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;
@@ -450,10 +463,7 @@
     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;
 
@@ -566,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 1422cfa..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"
@@ -77,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 {
@@ -89,7 +89,9 @@
 }
 
 bool VSyncPredictor::validate(nsecs_t timestamp) const {
+    SFTRACE_CALL();
     if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
+        SFTRACE_INSTANT("timestamp valid (first)");
         return true;
     }
 
@@ -98,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;
     }
 
@@ -109,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;
@@ -135,7 +141,7 @@
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard lock(mMutex);
 
@@ -148,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;
     }
 
@@ -235,7 +244,7 @@
 
     if (CC_UNLIKELY(bottom == 0)) {
         it->second = {idealPeriod(), 0};
-        clearTimestamps();
+        clearTimestamps(/* clearTimelines */ true);
         return false;
     }
 
@@ -245,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;
     }
 
@@ -297,7 +306,7 @@
 
 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());
@@ -330,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();
@@ -360,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);
         }
     }
@@ -370,7 +383,7 @@
 }
 
 void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) {
-    ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
+    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;
@@ -378,7 +391,7 @@
     const auto renderPeriodDelta =
             prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
     if (applyImmediately) {
-        ATRACE_FORMAT_INSTANT("applyImmediately");
+        SFTRACE_FORMAT_INSTANT("applyImmediately");
         while (mTimelines.size() > 1) {
             mTimelines.pop_front();
         }
@@ -390,13 +403,20 @@
     const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
             mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
     if (newRenderRateIsHigher) {
-        ATRACE_FORMAT_INSTANT("newRenderRateIsHigher");
+        SFTRACE_FORMAT_INSTANT("newRenderRateIsHigher");
         mTimelines.clear();
         mLastCommittedVsync = TimePoint::fromNs(0);
 
     } else {
-        mTimelines.back().freeze(
-                TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        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()));
@@ -405,7 +425,7 @@
 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;
@@ -414,6 +434,9 @@
           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());
@@ -427,13 +450,15 @@
         mRateMap[idealPeriod()] = {idealPeriod(), 0};
     }
 
-    mTimelines.clear();
-    clearTimestamps();
+    if (clearTimelines) {
+      mTimelines.clear();
+    }
+    clearTimestamps(clearTimelines);
 }
 
 Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
                                                       TimePoint lastConfirmedPresentTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (mNumVsyncsForFrame <= 1) {
         return 0ns;
@@ -441,20 +466,21 @@
 
     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;
@@ -465,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;
@@ -475,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;
@@ -497,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 {
@@ -507,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()};
@@ -514,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;
@@ -539,15 +570,19 @@
     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();
@@ -558,7 +593,7 @@
     if (mTimelines.empty()) {
         mLastCommittedVsync = TimePoint::fromNs(0);
         mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
-    } else {
+    } else if (clearTimelines) {
         while (mTimelines.size() > 1) {
             mTimelines.pop_front();
         }
@@ -579,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 {
@@ -602,7 +638,7 @@
     if (mRenderRateOpt &&
         mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
                 mClock->now()) {
-        ATRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
+        SFTRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
         mTimelines.clear();
         mLastCommittedVsync = TimePoint::fromNs(0);
         mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
@@ -611,7 +647,10 @@
 
     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;
@@ -635,16 +674,16 @@
 
 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, std::optional<Period> minFramePeriodOpt, nsecs_t vsync,
         MissedVsync missedVsync, std::optional<nsecs_t> lastVsyncOpt) {
-    ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
+    SFTRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
 
     nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
     const auto threshold = model.slope / 2;
@@ -656,34 +695,43 @@
         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. There is no need to to shift the vsync timeline again.
-            vsyncTime += missedVsync.fixup.ns();
-            ATRACE_FORMAT_INSTANT("lastFrameMissed");
+            // 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) {
-            // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
-            // first before trying to use it.
-            lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *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
-                ATRACE_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);
+                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;
     }
 
@@ -737,7 +785,9 @@
         return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
     };
 
-    Fps displayFps = 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();
 
@@ -745,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 8ce61d8..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>
 
@@ -77,7 +78,7 @@
 
     void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex);
 
-    void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
+    void onFrameBegin(TimePoint expectedPresentTime, FrameTime lastSignaledFrameTime) final
             EXCLUDES(mMutex);
     void onFrameMissed(TimePoint expectedPresentTime) final EXCLUDES(mMutex);
 
@@ -103,9 +104,16 @@
         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);
@@ -119,7 +127,7 @@
 
     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;
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 8038364..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,19 +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 && mTracker.isCurrentMode(modePtr) && !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;
@@ -191,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;
@@ -205,12 +206,12 @@
         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();
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 134d28e..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"
 
@@ -112,8 +113,7 @@
      */
     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/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index 2c397bd..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>
@@ -54,31 +55,20 @@
 
     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).
-    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; }
-    TimePoint lastSignaledFrameTime() const { return mLastSignaledFrameTime; };
+    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 {
@@ -87,8 +77,7 @@
 
     void addFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime,
                   TimePoint expectedPresentTime) {
-        mFenceWithFenceTimes.next() = {std::move(presentFence), presentFenceTime,
-                                       expectedPresentTime};
+        mPresentFences.next() = {std::move(presentFence), presentFenceTime, expectedPresentTime};
     }
 
     VsyncId mVsyncId;
@@ -100,17 +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();
     };
-    // size should be longest sf-duration / shortest vsync period and round up
-    std::array<FenceWithFenceTime, 5> mPresentFences; // currently consider 166hz.
-    utils::RingBuffer<FenceWithFenceTime, 5> mFenceWithFenceTimes;
 
-    TimePoint mLastSignaledFrameTime;
+    // 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;
@@ -120,30 +117,6 @@
         static_assert(N > 1);
         return expectedFrameDuration() > (N - 1) * minFramePeriod;
     }
-
-    const FenceTimePtr pastVsyncTimePtr() const {
-        auto pastFenceTimePtr = FenceTime::NO_FENCE;
-        for (size_t i = 0; i < mFenceWithFenceTimes.size(); i++) {
-            const auto& [_, fenceTimePtr, expectedPresentTime] = mFenceWithFenceTimes[i];
-            if (expectedPresentTime > mFrameBeginTime) {
-                return pastFenceTimePtr;
-            }
-            pastFenceTimePtr = fenceTimePtr;
-        }
-        return pastFenceTimePtr;
-    }
-
-    size_t getPresentFenceShift(Period minFramePeriod) const {
-        const bool isTwoVsyncsAhead = targetsVsyncsAhead<2>(minFramePeriod);
-        size_t shift = 0;
-        if (isTwoVsyncsAhead) {
-            shift = static_cast<size_t>(expectedFrameDuration().ns() / minFramePeriod.ns());
-            if (shift >= mPresentFences.size()) {
-                shift = mPresentFences.size() - 1;
-            }
-        }
-        return shift;
-    }
 };
 
 // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
@@ -166,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 7036e67..3ee1e54 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -14,13 +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),
@@ -28,34 +29,53 @@
         mHwcFrameMissed("PrevHwcFrameMissed " + displayLabel, false),
         mGpuFrameMissed("PrevGpuFrameMissed " + displayLabel, false) {}
 
-TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const {
-    // TODO(b/267315508): Generalize to N VSYNCs.
-    const size_t shift = getPresentFenceShift(minFramePeriod);
-    return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
-}
-
-FenceTimePtr FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
-    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
-        return pastVsyncTimePtr();
+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]};
     }
 
-    const size_t shift = getPresentFenceShift(minFramePeriod);
-    ATRACE_FORMAT("mPresentFences shift=%zu", shift);
-    return mPresentFences[shift].fenceTime;
+    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{}};
 }
 
-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.
-    const bool allowNVsyncs = FlagManager::getInstance().allow_n_vsyncs_in_targeter();
-    if (!allowNVsyncs && targetsVsyncsAhead<3>(minFramePeriod)) {
+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) {
@@ -89,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
@@ -117,9 +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 = TimePoint::fromNs(pastPresentTime);
+        mLastSignaledFrameTime = {.signalTime = TimePoint::fromNs(pastPresentTime),
+                                  .expectedPresentTime = fence.expectedPresentTime};
         const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
         return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
     }();
@@ -130,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 {};
@@ -153,10 +189,8 @@
     if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
         addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime);
     } else {
-        for (size_t i = mPresentFences.size()-1; i >= 1; i--) {
-            mPresentFences[i] = mPresentFences[i-1];
-        }
-        mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
+        mPresentFencesLegacy[1] = mPresentFencesLegacy[0];
+        mPresentFencesLegacy[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
     }
     return presentFenceTime;
 }
@@ -169,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 5448eec..6f4e1f1 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -53,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 {
@@ -169,7 +174,6 @@
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsync) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{111};
     TimePoint frameBeginTime(1000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -177,16 +181,72 @@
     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);
     }
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -194,101 +254,66 @@
     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, recallsPastNVsyncTwoVsyncsAhead) {
+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 Period kPeriod = kRefreshRate.getPeriod();
-    constexpr Duration kFrameDuration = 10ms;
+    constexpr Duration kFrameDuration = 40ms;
 
-    FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
+    FenceTimePtr firstFence = FenceTime::NO_FENCE;
     for (int n = 5; n-- > 0;) {
         Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
         const auto fence = frame.end();
-
-        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
-        EXPECT_EQ(target().presentFenceForPastVsync(kFrameDuration), previousFence);
-
-        frameBeginTime += kPeriod;
-        previousFence = fence;
+        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);
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
 
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
-    constexpr Fps kPeakRefreshRate = 240_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;
-    }
-}
-
-TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAheadVrr) {
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-    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 = 10ms;
-
-    FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
-    for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
-                    kPeakRefreshRate);
-        const auto fence = frame.end();
-
-        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
-        EXPECT_EQ(target().presentFenceForPastVsync(kFrameDuration), previousFence);
-
-        frameBeginTime += kPeriod;
-        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) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -296,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);
@@ -318,7 +380,6 @@
 // Same as `detectsEarlyPresent`, above, but verifies that we do not set an earliest present time
 // when there is expected present time support.
 TEST_F(FrameTargeterWithExpectedPresentSupportTest, detectsEarlyPresent) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -326,26 +387,30 @@
 
     // 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());
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{444};
     TimePoint frameBeginTime(4000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -353,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); }
@@ -371,66 +440,21 @@
     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);
 }
 
-TEST_F(FrameTargeterTest, detectsEarlyPresentNVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
-    VsyncId vsyncId{444};
-    TimePoint frameBeginTime(4000ms);
-    Fps refreshRate = 120_Hz;
-    Period period = refreshRate.getPeriod();
-
-    // The target is not early while past present fences are pending.
-    for (int n = 5; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
-        EXPECT_FALSE(wouldPresentEarly(period));
-        EXPECT_FALSE(target().earliestPresentTime());
-    }
-
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
-    auto fence = frame.end();
-    frameBeginTime += period;
-    fence->signalForTest(frameBeginTime.ns());
-
-    // The target is two VSYNCs ahead, so the past present fence is still pending.
-    EXPECT_FALSE(wouldPresentEarly(period));
-    EXPECT_FALSE(target().earliestPresentTime());
-
-    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate); }
-
-    Frame oneEarlyPresentFrame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
-    // The target is early if the past present fence was signaled.
-    EXPECT_TRUE(wouldPresentEarly(period));
-    ASSERT_NE(std::nullopt, target().earliestPresentTime());
-    EXPECT_EQ(*target().earliestPresentTime(),
-              target().expectedPresentTime() - period - kHwcMinWorkDuration);
-
-    fence = oneEarlyPresentFrame.end();
-    frameBeginTime += period;
-    fence->signalForTest(frameBeginTime.ns());
-
-    // Change rate to track frame more than 2 vsyncs ahead
-    refreshRate = 144_Hz;
-    period = refreshRate.getPeriod();
-    Frame onePresentEarlyFrame(this, vsyncId++, frameBeginTime, 16ms, refreshRate, refreshRate);
-    // The target is not early as last frame as the past frame is tracked for pending.
-    EXPECT_FALSE(wouldPresentEarly(period));
-}
-
 TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     TimePoint frameBeginTime(5000ms);
     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 8bb72b8..41a9a1b 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -93,6 +93,12 @@
     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/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index abab49d..6cce334 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,8 +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>
@@ -63,7 +65,7 @@
 #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>
@@ -71,9 +73,8 @@
 #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>
@@ -143,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"
@@ -204,8 +206,6 @@
 using ui::DisplayPrimaries;
 using ui::RenderIntent;
 
-using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController;
-
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace {
@@ -373,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;
@@ -433,7 +431,7 @@
 }
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipInitialization) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGI("SurfaceFlinger is starting");
 
     hasSyncFramework = running_without_sync_framework(true);
@@ -533,13 +531,7 @@
 
     mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
 
-    mLayerLifecycleManagerEnabled =
-            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
-
     // 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);
 }
 
@@ -728,6 +720,7 @@
     mBootFinished = true;
     FlagManager::getMutableInstance().markBootCompleted();
 
+    ::tracing_perfetto::registerWithPerfetto();
     mInitBootPropsFuture.wait();
     mRenderEnginePrimeCacheFuture.wait();
 
@@ -854,13 +847,15 @@
     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();
@@ -912,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);
@@ -1006,6 +1003,8 @@
             // 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);
         });
 
@@ -1280,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;
         }
     }
 
@@ -1312,7 +1305,7 @@
     const auto mode = desiredMode.mode;
     const auto displayId = mode.modePtr->getPhysicalDisplayId();
 
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const bool emitEvent = desiredMode.emitEvent;
 
@@ -1340,22 +1333,16 @@
             // 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 DesiredModeAction::InitiateRenderRateSwitch:
             mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false);
-
-            if (displayId == mActiveDisplayId) {
-                mScheduler->updatePhaseConfiguration(mode.fps);
-            }
+            mScheduler->updatePhaseConfiguration(displayId, mode.fps);
 
             if (emitEvent) {
-                dispatchDisplayModeChangeEvent(displayId, mode);
+                mScheduler->onDisplayModeChanged(displayId, mode);
             }
             break;
         case DesiredModeAction::None:
@@ -1365,7 +1352,7 @@
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
                                                    DisplayModeId modeId, Fps minFps, Fps maxFps) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
@@ -1414,8 +1401,10 @@
     return future.get();
 }
 
+// TODO: b/241285876 - Restore thread safety analysis once mStateLock below is unconditional.
+[[clang::no_thread_safety_analysis]]
 void SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const auto pendingModeOpt = mDisplayModeController.getPendingMode(displayId);
     if (!pendingModeOpt) {
@@ -1429,7 +1418,7 @@
     if (const auto oldResolution =
                 mDisplayModeController.getActiveMode(displayId).modePtr->getResolution();
         oldResolution != activeMode.modePtr->getResolution()) {
-        Mutex::Autolock lock(mStateLock);
+        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
@@ -1445,12 +1434,10 @@
     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);
     }
 }
 
@@ -1471,19 +1458,15 @@
 
     constexpr bool kAllowToEnable = true;
     mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
-    mScheduler->setRenderRate(displayId, renderFps, /*applyImmediately*/ true);
 
-    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& [displayId, physical] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) {
+    for (const auto& [displayId, physical] : mPhysicalDisplays) {
         auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
         if (!desiredModeOpt) {
             continue;
@@ -1536,21 +1519,14 @@
         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 = displayId;
-        }
-    }
+            // HWC has requested to apply the mode change immediately rather than on the next frame.
+            finalizeDisplayModeChange(displayId);
 
-    if (displayToUpdateImmediately) {
-        const auto displayId = *displayToUpdateImmediately;
-        finalizeDisplayModeChange(displayId);
-
-        const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
-        if (desiredModeOpt &&
-            mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
-            applyActiveMode(displayId);
+            const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
+            if (desiredModeOpt &&
+                mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+                applyActiveMode(displayId);
+            }
         }
     }
 }
@@ -1558,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;
@@ -2169,12 +2145,12 @@
     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) {
@@ -2191,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)) {
@@ -2257,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);
     }
 }
@@ -2283,12 +2243,12 @@
 }
 
 void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mScheduler->forceNextResync();
 }
 
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const char* const whence = __func__;
     static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                    kMainThreadContext) {
@@ -2297,7 +2257,7 @@
                 const Fps refreshRate = Fps::fromPeriodNsecs(
                         getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos
                                                                         : data.vsyncPeriodNanos);
-                ATRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
+                SFTRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
 
                 const auto renderRate = mDisplayModeController.getActiveMode(*displayIdOpt).fps;
                 constexpr bool kSetByHwc = true;
@@ -2307,6 +2267,18 @@
     }));
 }
 
+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);
+    }
+}
+
 void SurfaceFlinger::configure() {
     Mutex::Autolock lock(mStateLock);
     if (configureLocked()) {
@@ -2314,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;
@@ -2416,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
@@ -2433,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();
@@ -2452,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);
     }
@@ -2471,7 +2409,7 @@
     mustComposite |= applyAndCommitDisplayTransactionStatesLocked(update.transactions);
 
     {
-        ATRACE_NAME("LayerSnapshotBuilder:update");
+        SFTRACE_NAME("LayerSnapshotBuilder:update");
         frontend::LayerSnapshotBuilder::Args
                 args{.root = mLayerHierarchyBuilder.getHierarchy(),
                      .layerLifecycleManager = mLayerLifecycleManager,
@@ -2512,8 +2450,8 @@
     }
 
     bool newDataLatched = false;
-    ATRACE_NAME("DisplayCallbackAndStatsUpdates");
-    mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
+    SFTRACE_NAME("DisplayCallbackAndStatsUpdates");
+    mustComposite |= applyTransactionsLocked(update.transactions);
     traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
     const nsecs_t latchTime = systemTime();
     bool unused = false;
@@ -2560,17 +2498,25 @@
         it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
         newDataLatched = true;
 
-        mLayersWithQueuedFrames.emplace(it->second);
+        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.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-        if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) == mLayersIdsWithQueuedFrames.end())
-            return;
-        Region visibleReg;
-        visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
-        invalidateLayerStack(snapshot.outputFilter, visibleReg);
+    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()) {
@@ -2578,7 +2524,7 @@
     }
 
     {
-        ATRACE_NAME("LLM:commitChanges");
+        SFTRACE_NAME("LayerLifecycleManager:commitChanges");
         mLayerLifecycleManager.commitChanges();
     }
 
@@ -2601,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();
@@ -2618,19 +2564,26 @@
         return false;
     }
 
-    for (const auto [displayId, _] : frameTargets) {
-        if (mDisplayModeController.isModeSetPending(displayId)) {
-            finalizeDisplayModeChange(displayId);
+    {
+        ConditionalLock lock(mStateLock, FlagManager::getInstance().connected_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;
         }
     }
@@ -2677,11 +2630,8 @@
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
         bool transactionsAreEmpty = false;
-        if (mLayerLifecycleManagerEnabled) {
-            mustComposite |=
-                    updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
-                                         flushTransactions, transactionsAreEmpty);
-        }
+        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
@@ -2715,13 +2665,18 @@
         mUpdateAttachedChoreographer = false;
 
         Mutex::Autolock lock(mStateLock);
-        mScheduler->chooseRefreshRateForContent(mLayerLifecycleManagerEnabled
-                                                        ? &mLayerHierarchyBuilder.getHierarchy()
-                                                        : nullptr,
+        mScheduler->chooseRefreshRateForContent(&mLayerHierarchyBuilder.getHierarchy(),
                                                 updateAttachedChoreographer);
+
+        if (FlagManager::getInstance().connected_display()) {
+            initiateDisplayModeChanges();
+        }
     }
 
-    initiateDisplayModeChanges();
+    if (!FlagManager::getInstance().connected_display()) {
+        ftl::FakeGuard guard(mStateLock);
+        initiateDisplayModeChanges();
+    }
 
     updateCursorAsync();
     if (!mustComposite) {
@@ -2743,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;
@@ -2780,17 +2735,14 @@
 
     const bool updateTaskMetadata = mCompositionEngine->getFeatureFlags().test(
             compositionengine::Feature::kSnapshotLayerMetadata);
-    if (updateTaskMetadata && (mVisibleRegionsDirty || mLayerMetadataSnapshotNeeded)) {
-        updateLayerMetadataSnapshot();
-        mLayerMetadataSnapshotNeeded = false;
-    }
 
     refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache);
 
     if (!FlagManager::getInstance().ce_fence_promise()) {
         refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
-        for (auto& layer : mLayersWithQueuedFrames) {
-            if (const auto& layerFE = layer->getCompositionEngineLayerFE())
+        for (auto& [layer, _] : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE(
+                        {static_cast<uint32_t>(layer->sequence)}))
                 refreshArgs.layersWithQueuedFrames.push_back(layerFE);
         }
     }
@@ -2831,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;
@@ -2865,8 +2817,9 @@
         }
 
         refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
-        for (auto& layer : mLayersWithQueuedFrames) {
-            if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+        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() ==
@@ -2897,9 +2850,7 @@
         for (auto [layer, layerFE] : layers) {
             CompositionResult compositionResult{layerFE->stealCompositionResult()};
             for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
-                Layer* clonedFrom = layer->getClonedFrom().get();
-                auto owningLayer = clonedFrom ? clonedFrom : layer;
-                owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+                layer->onLayerDisplayed(std::move(releaseFence), layerStack);
             }
             if (compositionResult.lastClientCompositionFence) {
                 layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
@@ -2907,6 +2858,7 @@
         }
     }
 
+    SFTRACE_NAME("postComposition");
     mTimeStats->recordFrameDuration(pacesetterTarget.frameBeginTime().ns(), systemTime());
 
     // Send a power hint after presentation is finished.
@@ -2914,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()),
@@ -3001,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
@@ -3086,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;
@@ -3182,10 +3122,10 @@
     }
     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());
     }
 
@@ -3246,28 +3186,20 @@
                         }
                     };
 
-            if (mLayerLifecycleManagerEnabled) {
-                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);
+            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);
         }
     }
@@ -3313,9 +3245,8 @@
             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);
@@ -3327,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
@@ -3380,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;
@@ -3660,7 +3539,12 @@
     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);
 
@@ -3684,16 +3568,6 @@
     mPhysicalDisplays.erase(displayId);
 }
 
-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;
-
-    ((*mScheduler).*onDisplayModeChanged)(scheduler::Cycle::Render, mode);
-}
-
 sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
         const wp<IBinder>& displayToken,
         std::shared_ptr<compositionengine::Display> compositionDisplay,
@@ -3769,7 +3643,7 @@
     if (const auto& physical = state.physical) {
         const auto& mode = *physical->activeMode;
         mDisplayModeController.setActiveMode(physical->id, mode.getId(), mode.getVsyncRate(),
-                                             mode.getVsyncRate());
+                                             mode.getPeakFps());
     }
 
     display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack));
@@ -3839,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);
@@ -3855,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()) {
@@ -3894,7 +3778,7 @@
         if (display->isVirtual()) {
             releaseVirtualDisplay(display->getVirtualId());
         } else {
-            mScheduler->unregisterDisplay(display->getPhysicalId());
+            mScheduler->unregisterDisplay(display->getPhysicalId(), mActiveDisplayId);
         }
     }
 
@@ -3935,7 +3819,8 @@
         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);
@@ -3946,11 +3831,8 @@
                 setPowerModeInternal(display, hal::PowerMode::ON);
             }
 
-            // TODO(b/175678251) Call a listener instead.
-            if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                const Fps refreshRate =
-                        mDisplayModeController.getActiveMode(display->getPhysicalId()).fps;
-                mScheduler->resetPhaseConfiguration(refreshRate);
+            if (display->getPhysicalId() == mActiveDisplayId) {
+                onActiveDisplayChangedLocked(nullptr, *display);
             }
         }
         return;
@@ -4034,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;
@@ -4049,51 +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) {
-                layer->updateTransformHint(hintDisplay->getTransformHint());
-            }
-        });
-    }
-
     if (mLayersAdded) {
         mLayersAdded = false;
         // Layers have been added.
@@ -4107,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) {
@@ -4128,7 +3949,7 @@
     if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::vector<WindowInfo> windowInfos;
     std::vector<DisplayInfo> displayInfos;
@@ -4158,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),
@@ -4221,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) FTL_FAKE_GUARD(kMainThreadContext) {
-            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();
 
@@ -4271,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
@@ -4296,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,
@@ -4439,6 +4236,8 @@
             if (data.hintStatus.compare_exchange_strong(scheduleHintOnTx,
                                                         NotifyExpectedPresentHintStatus::Sent)) {
                 sendHint();
+                constexpr bool kAllowToEnable = true;
+                mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable);
             }
         }));
     }
@@ -4458,7 +4257,7 @@
     scheduleNotifyExpectedPresentHint(displayId);
 }
 
-void SurfaceFlinger::onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) {
+void SurfaceFlinger::onCommitNotComposited() {
     if (FlagManager::getInstance().commit_not_composited()) {
         mFrameTimeline->onCommitNotComposited();
     }
@@ -4506,7 +4305,8 @@
                                              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,
                                   /*applyImmediately*/ true);
@@ -4537,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) {
@@ -4617,160 +4351,30 @@
     }
 }
 
-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) {
+        static std::atomic<nsecs_t> lasttime{0};
+        nsecs_t now = systemTime();
+        if (lasttime != 0 && ns2s(now - lasttime.load()) < 10) {
+            ALOGE("AddClientLayer already dumped 10s before");
+            return NO_MEMORY;
+        } else {
+            lasttime = now;
+        }
+
         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));
-                }
-            }
+        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;
     }
 
-    layer->updateTransformHint(mActiveDisplayTransformHint);
     if (outTransformHint) {
         *outTransformHint = mActiveDisplayTransformHint;
     }
@@ -4778,7 +4382,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();
@@ -4795,7 +4399,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;
 }
 
@@ -4803,7 +4407,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);
@@ -4828,8 +4432,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;
     }
 
@@ -4841,117 +4445,22 @@
     // 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()) {
-                    // Used to add a stalled transaction which uses an internal lock.
-                    ftl::FakeGuard guard(kMainThreadContext);
-                    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;
@@ -4973,8 +4482,8 @@
                             uint32_t currentMaxAcquiredBufferCount =
                                     getMaxAcquiredBufferCountForCurrentRefreshRate(
                                             layer->ownerUid.val());
-                            ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
-                                                  layer->name.c_str(), s.bufferData->frameNumber);
+                            SFTRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+                                                   layer->name.c_str(), s.bufferData->frameNumber);
                             s.bufferData->releaseBufferListener
                                     ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()
                                                                ->getId(),
@@ -4988,9 +4497,9 @@
                         // 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);
+                        SFTRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                                       layer->name.c_str(), layer->barrierProducerId,
+                                       s.bufferData->producerId);
                         return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
                     }
 
@@ -5000,10 +4509,10 @@
                                 ((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);
+                            SFTRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64
+                                           " > %" PRId64,
+                                           layer->name.c_str(), layer->barrierFrameNumber,
+                                           s.bufferData->barrierFrameNumber);
                             ready = TransactionReadiness::NotReadyBarrier;
                             return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                         }
@@ -5016,7 +4525,7 @@
                         flushState.bufferLayersReadyToPresent.contains(s.surface.get());
                 if (layer->backpressureEnabled() && hasPendingBuffer &&
                     transaction.isAutoTimestamp) {
-                    ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
+                    SFTRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
                     ready = TransactionReadiness::NotReady;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
@@ -5033,8 +4542,8 @@
                                                   flushState.firstTransaction) &&
                             layer->isSimpleBufferUpdate(s);
                     if (allowLatchUnsignaled) {
-                        ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
-                                      layer->name.c_str());
+                        SFTRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                                       layer->name.c_str());
                         ready = TransactionReadiness::NotReadyUnsignaled;
                     } else {
                         ready = TransactionReadiness::NotReady;
@@ -5051,7 +4560,7 @@
                                                                 .frameNumber =
                                                                         s.bufferData->frameNumber});
                         }
-                        ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+                        SFTRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
                         return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                     }
                 }
@@ -5063,32 +4572,23 @@
 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) {
@@ -5132,22 +4632,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;
         }
 
@@ -5155,9 +4655,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;
         }
     }
@@ -5167,12 +4667,12 @@
 
 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();
@@ -5182,7 +4682,7 @@
         composerState.state.sanitize(permissions);
     }
 
-    for (DisplayState display : displays) {
+    for (DisplayState& display : displays) {
         display.sanitize(permissions);
     }
 
@@ -5303,11 +4803,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
@@ -5315,27 +4810,11 @@
     for (const auto& listener : listenerCallbacks) {
         mTransactionCallbackInvoker.addEmptyTransaction(listener);
     }
-    nsecs_t now = systemTime();
     uint32_t clientStateFlags = 0;
     for (auto& resolvedState : states) {
         clientStateFlags |=
                 updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, desiredPresentTime,
                                              isAutoTimestamp, postTime, transactionId);
-        if (!mLayerLifecycleManagerEnabled) {
-            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);
-                }
-            }
-        }
     }
 
     transactionFlags |= clientStateFlags;
@@ -5470,366 +4949,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::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;
-    }
-    if (what & layer_state_t::eMetadataChanged) {
-        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.trustedOverlay == gui::TrustedOverlay::ENABLED)) {
-            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, 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,
@@ -5866,10 +4985,8 @@
     }
     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;
     }
@@ -5884,6 +5001,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.
@@ -5898,7 +5026,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) {
@@ -5916,18 +5044,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, 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) {
@@ -5941,6 +5068,10 @@
         }
     }
 
+    if (what & layer_state_t::eBufferReleaseChannelChanged) {
+        layer->setBufferReleaseChannel(s.bufferReleaseChannel);
+    }
+
     const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence());
     bool willPresentCurrentTransaction = requestedLayerState &&
             (requestedLayerState->hasReadyFrame() ||
@@ -5979,8 +5110,6 @@
         if (result != NO_ERROR) {
             return result;
         }
-
-        mirrorLayer->setClonedChild(mirrorFrom->createClone());
     }
 
     outResult.layerId = mirrorLayer->sequence;
@@ -6094,31 +5223,22 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::markLayerPendingRemovalLocked(const sp<Layer>& layer) {
-    mLayersPendingRemoval.add(layer);
-    mLayersRemoved = true;
-    setTransactionFlags(eTransactionNeeded);
-}
-
 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);
 
-    Mutex::Autolock lock(mStateLock);
-    markLayerPendingRemovalLocked(layer);
+    std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+    mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
+
+    Mutex::Autolock stateLock(mStateLock);
     layer->onHandleDestroyed();
     mBufferCountTracker.remove(handle);
     layer.clear();
-
-    setTransactionFlags(eTransactionFlushNeeded);
+    setTransactionFlags(eTransactionFlushNeeded | eTransactionNeeded);
 }
 
 void SurfaceFlinger::initializeDisplays() {
@@ -6389,15 +5509,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;
@@ -6414,7 +5542,7 @@
 }
 
 void SurfaceFlinger::dumpStats(const DumpArgs& args, std::string& result) const {
-    StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC());
+    StringAppendF(&result, "%" PRId64 "\n", mScheduler->getPacesetterVsyncPeriod().ns());
     if (args.size() < 2) return;
 
     const auto name = String8(args[1]);
@@ -6449,8 +5577,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 {
@@ -6474,11 +5602,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 {
@@ -6641,53 +5764,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 {
@@ -6702,9 +5807,16 @@
         }
     }
 
-    return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
-                                           mLegacyLayers, traceFlags)
-            .generate(mLayerHierarchyBuilder.getHierarchy());
+    auto traceGenerator =
+            LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
+                                            mLegacyLayers, traceFlags)
+                    .with(mLayerHierarchyBuilder.getHierarchy());
+
+    if (traceFlags & LayerTracing::Flag::TRACE_EXTRA) {
+        traceGenerator.withOffscreenLayers(mLayerHierarchyBuilder.getOffscreenHierarchy());
+    }
+
+    return traceGenerator.generate();
 }
 
 google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
@@ -6738,26 +5850,6 @@
     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]() FTL_FAKE_GUARD(kMainThreadContext) {
@@ -6766,42 +5858,8 @@
             .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");
-    }
-}
-
 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;
@@ -6880,8 +5938,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);
@@ -6980,15 +6037,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) {
@@ -7482,15 +6530,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
@@ -7679,6 +6721,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) {
@@ -7687,22 +6748,13 @@
         }
         if (!display->isRefreshRateOverlayEnabled()) return;
 
-        const auto desiredModeIdOpt =
-                mDisplayModeController.getDesiredMode(display->getPhysicalId())
-                        .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(PhysicalDisplayId 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);
@@ -7726,63 +6778,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:
@@ -7808,7 +6803,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;
     }
 
@@ -7901,9 +6897,10 @@
 
 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);
@@ -7916,7 +6913,7 @@
         return;
     }
 
-    if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+    if (captureArgs.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
         ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
@@ -7942,7 +6939,7 @@
             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);
@@ -7955,14 +6952,22 @@
     }
 
     GetLayerSnapshotsFunction getLayerSnapshotsFn =
-            getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds));
+            getLayerSnapshotsForScreenshots(layerStack, captureArgs.uid,
+                                            std::move(excludeLayerIds));
 
+    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>,
-                                                 args.sourceCrop, reqSize, args.dataspace,
-                                                 args.hintForSeamlessTransition,
-                                                 args.captureSecureLayers, displayWeak),
-                        getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected,
-                        args.grayscale, captureListener);
+                                                 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,
@@ -8011,12 +7016,15 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
+    ftl::Flags<RenderArea::Options> options;
+    if (args.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
     captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
-                                                 Rect(), size, args.dataspace,
-                                                 args.hintForSeamlessTransition,
-                                                 false /* captureSecureLayers */, displayWeak),
-                        getLayerSnapshotsFn, size, args.pixelFormat, kAllowProtected, kGrayscale,
-                        captureListener);
+                                                 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) {
@@ -8027,22 +7035,25 @@
 
 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()) {
+    if (captureArgs.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
         ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
@@ -8059,26 +7070,27 @@
         }
 
         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);
@@ -8104,8 +7116,9 @@
     }
 
     GetLayerSnapshotsFunction getLayerSnapshotsFn =
-            getLayerSnapshotsForScreenshots(parent->sequence, args.uid, std::move(excludeLayerIds),
-                                            args.childrenOnly, parentCrop);
+            getLayerSnapshotsForScreenshots(parent->sequence, captureArgs.uid,
+                                            std::move(excludeLayerIds), args.childrenOnly,
+                                            parentCrop);
 
     if (captureListener == nullptr) {
         ALOGD("capture screen must provide a capture listener callback");
@@ -8113,12 +7126,17 @@
         return;
     }
 
+    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, args.captureSecureLayers,
-                                                 args.hintForSeamlessTransition, parent,
-                                                 args.childrenOnly),
-                        getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected,
-                        args.grayscale, captureListener);
+                                                 reqSize, dataspace, parent, args.childrenOnly,
+                                                 options),
+                        getLayerSnapshotsFn, reqSize,
+                        static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
+                        captureArgs.allowProtected, captureArgs.grayscale,
+                        captureArgs.attachGainmap, captureListener);
 }
 
 // Creates a Future release fence for a layer and keeps track of it in a list to
@@ -8127,9 +7145,7 @@
 void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE,
                                                      ui::LayerStack layerStack) {
     ftl::Future<FenceResult> futureFence = layerFE->createReleaseFenceFuture();
-    Layer* clonedFrom = layer->getClonedFrom().get();
-    auto owningLayer = clonedFrom ? clonedFrom : layer;
-    owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
+    layer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
 }
 
 // Loop over all visible layers to see whether there's any protected layer. A protected layer is
@@ -8152,12 +7168,12 @@
 // 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::getDisplayAndLayerSnapshotsFromMainThread(
+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);
@@ -8171,9 +7187,9 @@
 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
@@ -8187,8 +7203,7 @@
         FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) {
         std::vector<sp<LayerFE>> layerFEs;
         auto displayState =
-                getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn,
-                                                          layerFEs);
+                getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layerFEs);
 
         const bool supportsProtected = getRenderEngine().supportsProtectedContent();
         bool hasProtectedLayer = false;
@@ -8218,9 +7233,9 @@
                 renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
                                                      renderengine::impl::ExternalTexture::Usage::
                                                              WRITEABLE);
-        auto futureFence =
-                captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */, grayscale,
-                                  isProtected, captureListener, displayState, layerFEs);
+        auto futureFence = captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */,
+                                             grayscale, isProtected, attachGainmap, captureListener,
+                                             displayState, layerFEs);
         futureFence.get();
 
     } else {
@@ -8255,7 +7270,7 @@
                                                              WRITEABLE);
         auto futureFence = captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, texture,
                                                    false /* regionSampling */, grayscale,
-                                                   isProtected, captureListener);
+                                                   isProtected, attachGainmap, captureListener);
         futureFence.get();
     }
 }
@@ -8309,9 +7324,10 @@
 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,
+        bool grayscale, bool isProtected, bool attachGainmap,
+        const sp<IScreenCaptureListener>& captureListener,
         std::optional<OutputCompositionState>& displayState, std::vector<sp<LayerFE>>& layerFEs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     ScreenCaptureResults captureResults;
     std::unique_ptr<const RenderArea> renderArea =
@@ -8326,19 +7342,87 @@
         }
         return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
     }
+    float displayBrightnessNits = displayState.value().displayBrightnessNits;
+    float sdrWhitePointNits = displayState.value().sdrWhitePointNits;
 
     // Empty vector needed to pass into renderScreenImpl for legacy path
     std::vector<std::pair<Layer*, sp<android::LayerFE>>> layers;
     ftl::SharedFuture<FenceResult> renderFuture =
-            renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected,
-                             captureResults, displayState, layers, layerFEs);
+            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();
+        };
+    }
 
     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 {
+                .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);
                 })
@@ -8350,8 +7434,9 @@
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshotLegacy(
         RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
         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) {
+    SFTRACE_CALL();
 
     auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
                                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
@@ -8379,8 +7464,8 @@
 
         auto layerFEs = extractLayerFEs(layers);
         ftl::SharedFuture<FenceResult> renderFuture =
-                renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale,
-                                 isProtected, captureResults, displayState, layers, layerFEs);
+                renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
+                                 attachGainmap, captureResults, displayState, layers, layerFEs);
 
         if (captureListener) {
             // Defer blocking on renderFuture back to the Binder thread.
@@ -8410,12 +7495,11 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::unique_ptr<const RenderArea> renderArea,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
-        std::optional<OutputCompositionState>& displayState,
+        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) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     for (auto& layerFE : layerFEs) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
@@ -8591,65 +7675,13 @@
 }
 
 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();
@@ -8671,7 +7703,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);
 
@@ -8700,13 +7732,9 @@
     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);
@@ -8730,10 +7758,7 @@
     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;
 }
 
@@ -8764,7 +7789,7 @@
 
 status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                                     const gui::DisplayModeSpecs& specs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
@@ -8798,7 +7823,7 @@
 
 status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                                     gui::DisplayModeSpecs* outSpecs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken || !outSpecs) {
         return BAD_VALUE;
@@ -8825,17 +7850,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());
     }
@@ -8846,24 +7866,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) {
@@ -8893,13 +7895,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;
 }
 
@@ -9012,51 +8008,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;
@@ -9090,7 +8041,7 @@
 
 void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr,
                                                   const DisplayDevice& activeDisplay) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (inactiveDisplayPtr) {
         inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
@@ -9099,8 +8050,6 @@
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    mScheduler->resetPhaseConfiguration(mDisplayModeController.getActiveMode(mActiveDisplayId).fps);
-
     // TODO(b/255635711): Check for pending mode changes on other displays.
     mScheduler->setModeChangePending(false);
 
@@ -9224,170 +8173,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->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 (!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) FTL_FAKE_GUARD(
-                        kMainThreadContext) {
-                    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 (!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;
 }
 
@@ -9514,33 +8345,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) {
@@ -9562,7 +8366,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));
@@ -9571,9 +8375,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) {
@@ -10452,6 +9253,28 @@
     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 ee541c4..0808c12 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>
@@ -52,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>
@@ -134,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;
@@ -272,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.
@@ -291,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;
     }
@@ -312,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
@@ -392,11 +388,10 @@
 
     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) {
@@ -408,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;
@@ -428,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
@@ -449,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);
             }
@@ -559,7 +549,7 @@
     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,
@@ -682,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);
@@ -697,32 +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(PhysicalDisplayId pacesetterDisplayId) 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(PhysicalDisplayId) 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;
+
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
     // Show render rate with refresh rate overlay
@@ -737,9 +719,9 @@
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
 
-    void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) EXCLUDES(mStateLock);
+    void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) REQUIRES(mStateLock);
     void finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext)
-            EXCLUDES(mStateLock);
+            REQUIRES(mStateLock);
 
     void dropModeRequest(PhysicalDisplayId) REQUIRES(kMainThreadContext);
     void applyActiveMode(PhysicalDisplayId) REQUIRES(kMainThreadContext);
@@ -762,17 +744,11 @@
                                             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)
             REQUIRES(kMainThreadContext);
@@ -780,13 +756,9 @@
                                           const std::vector<std::pair<Layer*, LayerFE*>>& layers)
             REQUIRES(kMainThreadContext);
     // Return true if we must composite this frame
-    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
-                                    bool& out) 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) REQUIRES(kMainThreadContext);
-    frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
     void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) REQUIRES(kMainThreadContext);
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
@@ -813,9 +785,9 @@
             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);
 
@@ -825,16 +797,10 @@
     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)
@@ -849,11 +815,9 @@
     // 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)
+    bool applyTransactionsLocked(std::vector<TransactionState>& transactions)
             REQUIRES(mStateLock, kMainThreadContext);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
@@ -877,16 +841,11 @@
     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);
@@ -896,13 +855,13 @@
 
     using OutputCompositionState = compositionengine::impl::OutputCompositionState;
 
-    std::optional<OutputCompositionState> getDisplayAndLayerSnapshotsFromMainThread(
+    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, const sp<IScreenCaptureListener>&);
+                             bool grayscale, bool attachGainmap, const sp<IScreenCaptureListener>&);
 
     std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
             RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
@@ -916,29 +875,24 @@
     ftl::SharedFuture<FenceResult> captureScreenshot(
             const RenderAreaBuilderVariant& renderAreaBuilder,
             const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+            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::unique_ptr<const RenderArea>,
-            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, ScreenCaptureResults&,
-            std::optional<OutputCompositionState>& displayState,
+            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);
 
-    // 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&);
-
     void readPersistentProperties();
 
     uint32_t getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t uid) const;
@@ -987,18 +941,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);
     }
 
@@ -1098,13 +1046,6 @@
                                const DisplayDeviceState& drawingState)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&);
-
-    /*
-     * VSYNC
-     */
-    nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
-
     /*
      * Display identification
      */
@@ -1155,7 +1096,6 @@
     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 listLayers(std::string& result) const REQUIRES(kMainThreadContext);
@@ -1181,8 +1121,6 @@
 
     perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const
             REQUIRES(kMainThreadContext);
-    void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
-                                  uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
     google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const;
     void doActiveLayersTracingIfNeeded(bool isCompositionComputed, bool visibleRegionDirty,
                                        TimePoint, VsyncId) REQUIRES(kMainThreadContext);
@@ -1194,7 +1132,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);
@@ -1255,7 +1192,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.
@@ -1295,21 +1231,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
@@ -1324,7 +1261,6 @@
     };
 
     bool mIsHdcpViaNegVsync = false;
-    bool mIsHotplugErrViaNegVsync = false;
 
     std::mutex mHotplugMutex;
     std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mHotplugMutex);
@@ -1337,7 +1273,7 @@
 
     display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock);
 
-    // The inner or outer display for foldables, assuming they have mutually exclusive power states.
+    // The inner or outer display for foldables, while unfolded or folded, respectively.
     std::atomic<PhysicalDisplayId> mActiveDisplayId;
 
     display::DisplayModeController mDisplayModeController;
@@ -1438,38 +1374,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.
@@ -1503,8 +1412,6 @@
     }
 
     bool mPowerHintSessionEnabled;
-
-    bool mLayerLifecycleManagerEnabled = false;
     // Whether a display should be turned on when initialized
     bool mSkipPowerOnForQuiescent;
 
@@ -1512,6 +1419,8 @@
     frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext);
     frontend::LayerSnapshotBuilder mLayerSnapshotBuilder GUARDED_BY(kMainThreadContext);
 
+    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);
@@ -1693,6 +1602,11 @@
             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;
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 0bafb71..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(
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 222ae30..c6856ae 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -27,10 +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 {
@@ -64,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;
         }
@@ -80,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;
         }
@@ -111,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;
@@ -151,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;
 }
@@ -162,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;
@@ -199,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++;
@@ -212,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 cb7150b..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>
@@ -59,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);
 
@@ -77,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,
@@ -89,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/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 bcf1886..f9c99bf 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -18,6 +18,7 @@
         "libSurfaceFlingerProp",
         "server_configurable_flags",
         "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
     ],
     static_libs: [
         "librenderengine_includes",
@@ -27,6 +28,7 @@
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
+    export_shared_lib_headers: ["libtracing_perfetto"],
 }
 
 cc_library_static {
@@ -60,6 +62,7 @@
     shared_libs: [
         "server_configurable_flags",
         "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
     ],
     static_libs: [
         "libsurfaceflinger_common",
@@ -75,6 +78,7 @@
     shared_libs: [
         "server_configurable_flags",
         "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
     ],
     static_libs: [
         "libsurfaceflinger_common_test",
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 2e3273c..12d6138 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -136,23 +136,27 @@
     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
@@ -242,18 +246,22 @@
 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, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index ab7a474..a1be194 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -73,6 +73,7 @@
     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;
@@ -80,17 +81,20 @@
     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/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_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index f4d4ee9..102e2b6 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -4,10 +4,10 @@
 container: "system"
 
 flag {
-    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"
+  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 {
@@ -21,18 +21,29 @@
   }
  } # ce_fence_promise
 
- flag {
-   name: "commit_not_composited"
-   namespace: "core_graphics"
-   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: "commit_not_composited"
+  namespace: "core_graphics"
+  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 {
+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"
@@ -43,7 +54,7 @@
   }
 } # deprecate_vsync_sf
 
- flag {
+flag {
   name: "detached_mirror"
   namespace: "window_surfaces"
   description: "Ignore local transform when mirroring a partial hierarchy"
@@ -55,6 +66,17 @@
 } # 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."
@@ -96,11 +118,11 @@
 } # 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
+  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 {
@@ -114,6 +136,14 @@
   }
  } # 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"
@@ -126,6 +156,14 @@
 } # 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"
@@ -136,4 +174,15 @@
   }
 } # 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 38fc977..4d5c0fd 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -28,6 +28,7 @@
         "android.hardware.graphics.common-ndk_shared",
         "surfaceflinger_defaults",
         "libsurfaceflinger_common_test_deps",
+        "libsurfaceflinger_proto_deps",
     ],
     test_suites: ["device-tests"],
     srcs: [
@@ -58,14 +59,12 @@
         "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",
         "libsurfaceflinger_common",
     ],
@@ -121,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 ebe11fb..e6fed63 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -20,12 +20,13 @@
 
 #include <android/gui/ISurfaceComposer.h>
 #include <gtest/gtest.h>
-#include <gui/AidlStatusUtil.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>
@@ -276,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);
@@ -396,6 +397,56 @@
     }
 }
 
+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/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 5b056d0..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>
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 d97d433..6cc1c51 100644
--- a/services/surfaceflinger/tests/MirrorLayer_test.cpp
+++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp
@@ -20,6 +20,7 @@
 
 #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"
@@ -350,7 +351,7 @@
         // Capture just the mirror layer and child.
         LayerCaptureArgs captureArgs;
         captureArgs.layerHandle = mirrorParent->getHandle();
-        captureArgs.sourceCrop = childBounds;
+        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/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 9a78550..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;
 
@@ -1052,7 +1053,7 @@
 
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mirroredLayer->getHandle();
-    captureArgs.sourceCrop = Rect(0, 0, 1, 1);
+    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
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 af3cb9a..67a5247 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -16,6 +16,7 @@
 #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>
 
@@ -51,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;
@@ -65,6 +76,7 @@
                                                                     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)
 
                 static const std::string kDisplayName("VirtualDisplay");
                 vDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName,
@@ -76,7 +88,12 @@
                         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);
+#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()) {
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index cd66dd2..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;
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 98d5754..f1bd87c 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -58,6 +58,7 @@
     ],
     test_suites: ["device-tests"],
     static_libs: ["libc++fs"],
+    header_libs: ["surfaceflinger_tests_common_headers"],
     srcs: [
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
@@ -79,20 +80,16 @@
         "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",
@@ -115,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",
@@ -151,6 +146,7 @@
         "android.hardware.power-ndk_static",
         "librenderengine_deps",
         "libsurfaceflinger_common_test_deps",
+        "libsurfaceflinger_proto_deps",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
@@ -169,7 +165,6 @@
         "libframetimeline",
         "libgmock",
         "libgui_mocks",
-        "liblayers_proto",
         "libperfetto_client_experimental",
         "librenderengine",
         "librenderengine_mocks",
@@ -208,6 +203,7 @@
         "libsync",
         "libui",
         "libutils",
+        "libtracing_perfetto",
     ],
     header_libs: [
         "android.hardware.graphics.composer3-command-buffer",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 08973de..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,20 +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);
-    };
-
-    // TODO: Use SurfaceFlinger::getLayerSnapshotsForScreenshots instead of this legacy function
-    auto getLayerSnapshotsFn = 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;
@@ -458,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<
@@ -468,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);
     }
 
@@ -666,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) {
@@ -751,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*) {}
@@ -771,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());
@@ -788,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;
     }
 
@@ -816,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());
@@ -829,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;
     }
 
@@ -865,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);
@@ -913,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;
     }
 };
@@ -927,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); }
 };
 
 /* ------------------------------------------------------------------------
@@ -1012,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) {
@@ -1052,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;
     }
@@ -1075,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;
     }
 
@@ -1095,7 +1076,7 @@
 };
 
 struct EmptyScreenshotResultVariant {
-    static void setupLayerState(CompositionTest*, sp<Layer>) {}
+    static void setupLayerState(CompositionTest*, frontend::RequestedLayerState&) {}
 
     template <typename Case>
     static void setupCallExpectations(CompositionTest*) {}
@@ -1361,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/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/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 dac9265..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,9 @@
     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) {
@@ -446,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) {
@@ -458,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",
@@ -575,6 +609,8 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(59, presentFence1);
+
+    EXPECT_EQ(getLayerOneJankData().size(), 0u);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
@@ -603,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) {
@@ -633,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) {
@@ -661,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) {
@@ -691,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) {
@@ -721,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) {
@@ -751,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) {
@@ -782,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) {
@@ -814,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) {
@@ -858,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);
 }
 
 /*
@@ -1789,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.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 8b3303c..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,98 +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);
-    }
-
-    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);
-    }
-
-    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();
@@ -201,321 +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 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 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, 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);
-    }
-
     LayerLifecycleManager mLifecycleManager;
 };
 
@@ -523,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 a61fa1e..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,6 +543,335 @@
     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);
 
@@ -293,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);
@@ -805,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 {
@@ -838,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);
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
deleted file mode 100644
index 088d0d2..0000000
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ /dev/null
@@ -1,1573 +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;
-using android::mock::createVrrDisplayMode;
-
-// 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);
-    }
-
-    static constexpr auto kVrrModeId = DisplayModeId(2);
-    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;
-    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 infrequent, 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 infrequent, 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, 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 = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate(33_Hz, Layer::FrameRateCompatibility::Gte,
-                                                    Seamlessness::OnlySeamless,
-                                                    FrameRateCategory::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(1, 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(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, 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(LayerHistoryTest, 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 = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate(33_Hz, Layer::FrameRateCompatibility::Gte,
-                                                    Seamlessness::OnlySeamless,
-                                                    FrameRateCategory::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(1, 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(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, 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(LayerHistoryTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
-    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
-
-    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)));
-
-    // Set default to Min so it is obvious that the vote reset triggered.
-    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;
-    }
-
-    // There is only 1 LayerRequirement due to the disabled flag frame_rate_category_mrr.
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, 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);
-}
-
-TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
-    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
-
-    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 infrequent, 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) {
-    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
-
-    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 infrequent
-    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) {
-    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
-
-    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 infrequent, 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 still active due to front buffering, but it's infrequent.
-    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 bc15dec..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);
@@ -631,4 +619,14 @@
     }
 }
 
+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 8b9ac93..75d2fa3 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -27,6 +27,7 @@
 #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, ...)                                    \
@@ -56,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);
     }
@@ -281,21 +293,132 @@
     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) {
@@ -676,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);
@@ -854,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());
@@ -945,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());
@@ -1304,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(),
@@ -1435,4 +1718,221 @@
             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 e74f643..c879280 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -706,7 +706,7 @@
     testGpuScenario(config, res);
     EXPECT_EQ(res.gpuDurationNanos, 0L);
     EXPECT_EQ(res.cpuDurationNanos, 0L);
-    EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+    EXPECT_GE(res.durationNanos, toNanos(29ms + getErrorMargin()));
     EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
 }
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index cf9a7d3..adbd868 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -100,7 +100,9 @@
     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 = {}, Fps pacesetterFps = {}) const {
@@ -138,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> {
@@ -1562,7 +1566,7 @@
             // 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::Low, 60_Hz},
             {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
@@ -1609,6 +1613,77 @@
     }
 }
 
+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);
@@ -1766,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);
 
@@ -1884,7 +1996,7 @@
     // Gets touch boost
     EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
     EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
-    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_TouchBoost) {
@@ -1978,7 +2090,7 @@
     lr2.name = "Max";
     actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
     EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
-    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitCategory;
     lr1.frameRateCategory = FrameRateCategory::Normal;
@@ -2140,14 +2252,14 @@
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
             {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::Low, false, 60_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
 
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
             {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
@@ -2207,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},
     };
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 4fb0690..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"
@@ -77,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));
@@ -84,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));
@@ -118,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);
 }
@@ -161,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());
 
@@ -187,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) {
@@ -224,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;
@@ -245,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);
 
@@ -287,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];
 
@@ -343,12 +401,15 @@
 }
 
 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);
@@ -367,9 +428,7 @@
                                                                                kDisplay2Mode60},
                                                                  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();
@@ -411,10 +470,10 @@
     {
         // 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};
@@ -457,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>>;
 
@@ -585,7 +647,8 @@
                                 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, /*applyImmediately*/ false);
     vrrTracker->addVsyncTimestamp(0);
@@ -600,7 +663,8 @@
                                              TimePoint::fromNs(2000)));
 
     // Not crossing the min frame period
-    vrrTracker->onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+    vrrTracker->onFrameBegin(TimePoint::fromNs(2000),
+                             {TimePoint::fromNs(1500), TimePoint::fromNs(1500)});
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
                                              TimePoint::fromNs(2500)));
@@ -733,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));
 
@@ -820,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 ff7612e..d638024 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
@@ -28,7 +28,6 @@
 class ColorMatrixTest : public CommitAndCompositeTest {};
 
 TEST_F(ColorMatrixTest, colorMatrixChanged) {
-    mFlinger.enableLayerLifecycleManager();
     EXPECT_COLOR_MATRIX_CHANGED(true, true);
     mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
 
@@ -46,7 +45,6 @@
 }
 
 TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) {
-    mFlinger.enableLayerLifecycleManager();
     EXPECT_COLOR_MATRIX_CHANGED(true, true);
     mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index e5f2a91..2d3ebb4 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -97,7 +97,7 @@
     // 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) {
@@ -129,7 +129,7 @@
     // 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) {
@@ -159,7 +159,7 @@
     // Cleanup conditions
 
     // Creating the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 }
 
 // Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
index f8ad8e1..df8f68f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
@@ -38,7 +38,7 @@
     // Call Expectations
 
     // Destroying the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 0c3e875..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>();
@@ -87,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,
@@ -142,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>();
 
@@ -169,129 +204,139 @@
                             TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp);
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateWithRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
+    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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120);
-
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120);
-
-    mFlinger.commit();
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120);
-
-    mFlinger.commit();
-
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId120);
-}
-
-TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
-
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
-
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120));
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90_4K);
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).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, onModeChanged(_)).Times(2);
+
+    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));
 
@@ -310,61 +355,12 @@
 
     mFlinger.commit();
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90_4K);
-}
-
-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;
-}
-
-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;
-    }
-
-    ftl::FakeGuard guard(kMainThreadContext);
-
-    if (dmc->getActiveMode(displayId).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());
@@ -381,13 +377,11 @@
 
     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));
@@ -414,8 +408,7 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
     EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId60);
@@ -434,10 +427,6 @@
 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());
@@ -454,13 +443,11 @@
 
     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));
@@ -486,8 +473,7 @@
 
     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));
 
@@ -511,11 +497,6 @@
 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());
@@ -532,13 +513,11 @@
 
     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));
@@ -566,8 +545,8 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId120, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId120,
+                                                                               120_Hz)));
 
     EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId120);
 
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_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 933d03d..352000e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -239,7 +239,7 @@
         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)
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 198a5de..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,16 +101,12 @@
                                                                              mock::VSyncDispatch>(),
                                                                      std::move(controller),
                                                                      mockRequestHardwareVsync
-                                                                             .AsStdFunction())));
+                                                                             .AsStdFunction())),
+                                           activeDisplayId);
     }
 
     testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;
 
-    void unregisterDisplay(PhysicalDisplayId displayId) {
-        ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::unregisterDisplay(displayId);
-    }
-
     void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::setDisplayPowerMode(displayId, powerMode);
@@ -135,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();
@@ -178,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);
@@ -190,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 007383b..c043b88 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <algorithm>
 #include <chrono>
 #include <memory>
 #include <variant>
@@ -44,7 +43,6 @@
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "RenderArea.h"
-#include "Scheduler/MessageQueue.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "SurfaceFlinger.h"
 #include "TestableScheduler.h"
@@ -60,7 +58,6 @@
 
 #include "Scheduler/VSyncTracker.h"
 #include "Scheduler/VsyncController.h"
-#include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
@@ -88,9 +85,7 @@
 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 {
@@ -276,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);
 
@@ -326,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
      */
@@ -395,7 +369,7 @@
             targets.try_emplace(id, &frameTargeter.target());
             targeters.try_emplace(id, &frameTargeter);
         }
-
+        mFlinger->setTransactionFlags(eTransactionFlushNeeded);
         mFlinger->commit(displayId, targets);
 
         if (composite) {
@@ -500,24 +474,26 @@
         auto layers = getLayerSnapshotsFn();
         auto layerFEs = mFlinger->extractLayerFEs(layers);
 
-        return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling,
+        return mFlinger->renderScreenImpl(renderArea.get(), buffer, regionSampling,
                                           false /* grayscale */, false /* isProtected */,
-                                          captureResults, displayState, layers, layerFEs);
+                                          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->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;
     }
@@ -528,7 +504,7 @@
 
     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,
@@ -547,7 +523,7 @@
     }
 
     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) {
@@ -584,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);
@@ -620,6 +594,18 @@
         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.
      */
@@ -637,12 +623,24 @@
     void injectLegacyLayer(sp<Layer> 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 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);
     };
@@ -663,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; }
@@ -677,13 +677,17 @@
     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; }
@@ -691,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() {
@@ -699,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); }
 
@@ -709,10 +714,6 @@
         return mFlinger->initTransactionTraceWriter();
     }
 
-    // Needed since mLayerLifecycleManagerEnabled is false by default and must
-    // be enabled for tests to go through the new front end path.
-    void enableLayerLifecycleManager() { mFlinger->mLayerLifecycleManagerEnabled = true; }
-
     void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
                                          std::optional<Period> timeoutOpt) {
@@ -771,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>();
@@ -1118,8 +1118,8 @@
                 if (mFlinger.scheduler() && mSchedulerRegistration) {
                     mFlinger.scheduler()->registerDisplay(*physicalId,
                                                           mCreationArgs.refreshRateSelector,
-                                                          std::move(controller),
-                                                          std::move(tracker));
+                                                          std::move(controller), std::move(tracker),
+                                                          mFlinger.mutableActiveDisplayId());
                 }
             }
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 7fb9247..fab1f6d 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -17,6 +17,7 @@
 #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>
@@ -26,10 +27,10 @@
 #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"
@@ -55,6 +56,7 @@
 
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
         mFlinger.setupMockScheduler();
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
         mFlinger.flinger()->addTransactionReadyFilters();
     }
 
@@ -65,6 +67,7 @@
     }
 
     TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
 
     struct TransactionInfo {
         Vector<ComposerState> states;
@@ -102,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,
@@ -126,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
@@ -152,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;
@@ -214,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,
@@ -235,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,
@@ -323,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.
@@ -361,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) {
@@ -371,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,
@@ -380,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;
@@ -418,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;
@@ -427,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);
             }
 
@@ -458,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),
@@ -773,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 85b61f8..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,
-                         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/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 46733b9..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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 3b09554..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"
@@ -51,7 +52,7 @@
     bool isVSyncInPhase(nsecs_t, Fps) final { return false; }
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
     void setRenderRate(Fps, bool) final {}
-    void onFrameBegin(TimePoint, TimePoint) 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; };
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 5109ea6..918107d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -673,6 +673,36 @@
     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);
 
@@ -871,18 +901,22 @@
     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) {
@@ -913,7 +947,7 @@
 
     // SF starts to catch up
     EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
-    vrrTracker.onFrameBegin(TimePoint::fromNs(3000), TimePoint::fromNs(0));
+    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));
@@ -922,17 +956,20 @@
 
     // 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));
+    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));
+    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));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(6500),
+                            {TimePoint::fromNs(5500), TimePoint::fromNs(5500)});
 }
 
 TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) {
@@ -990,6 +1027,65 @@
     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/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/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 8dd1a34..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));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 002fa9f..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_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 dec5fa5..25dd68e 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -26,21 +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, (PhysicalDisplayId), (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(PhysicalDisplayId) 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 4f44d1b..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 {
@@ -37,7 +39,7 @@
     MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override));
     MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
     MOCK_METHOD(void, setRenderRate, (Fps, bool), (override));
-    MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (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));
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 07916b6..253bad7 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -74,8 +74,8 @@
     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];
             }
         }
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/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 9b30337..5c7c9f4 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -16,16 +16,15 @@
 
 #define LOG_TAG "VibratorHalControllerBenchmarks"
 
+#include <android/binder_process.h>
 #include <benchmark/benchmark.h>
-#include <binder/ProcessState.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;
@@ -115,8 +114,8 @@
 class VibratorBench : public Fixture {
 public:
     void SetUp(State& /*state*/) override {
-        android::ProcessState::self()->setThreadPoolMaxThreadCount(1);
-        android::ProcessState::self()->startThreadPool();
+        ABinderProcess_setThreadPoolMaxThreadCount(1);
+        ABinderProcess_startThreadPool();
         mController.init();
     }
 
@@ -388,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)});
             }
         }
@@ -533,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;
             }
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 15fde91..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;
 };
 
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 715c221..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;
 
 // -------------------------------------------------------------------------------------------------
 
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 3f89960..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;
@@ -1248,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(
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 9e67725..09b0a14 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1215,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;
@@ -1465,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;
@@ -1473,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.
@@ -1892,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
@@ -1910,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;
@@ -2006,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) &&
@@ -2015,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;
@@ -2067,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 48c0ae9..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 {
 
@@ -503,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);
 }