Merge "Remove obsolete BLASTBufferQueue constructor" into main
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 372008e..a5d176d 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",
@@ -134,6 +136,7 @@
         "main.cpp",
     ],
     required: [
+        "alloctop",
         "atrace",
         "bugreport_procdump",
         "default_screenshot",
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 4758607..feca18d 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
@@ -1264,6 +1267,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) {
@@ -1344,6 +1358,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) {
@@ -1425,6 +1441,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(),
@@ -1481,6 +1499,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() {
@@ -1510,6 +1531,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)) {
@@ -1773,6 +1796,8 @@
 
     DoKmsg();
 
+    DumpKernelMemoryAllocations();
+
     DumpShutdownCheckpoints();
 
     DumpIpAddrAndRules();
@@ -2453,6 +2478,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,
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 38a125b..59c4d53 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -410,7 +410,16 @@
     return Status::ok();
 }
 
-Status ServiceManager::checkService(const std::string& name, os::Service* outService) {
+Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
+
+    *outBinder = tryGetBinder(name, false).service;
+    // returns ok regardless of result for legacy reasons
+    return Status::ok();
+}
+
+Status ServiceManager::checkService2(const std::string& name, os::Service* outService) {
     SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
             PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 964abee..5c4d891 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -46,7 +46,8 @@
     // getService will try to start any services it cannot find
     binder::Status getService(const std::string& name, sp<IBinder>* outBinder) override;
     binder::Status getService2(const std::string& name, os::Service* outService) override;
-    binder::Status checkService(const std::string& name, os::Service* outService) override;
+    binder::Status checkService(const std::string& name, sp<IBinder>* outBinder) override;
+    binder::Status checkService2(const std::string& name, os::Service* outService) override;
     binder::Status addService(const std::string& name, const sp<IBinder>& binder,
                               bool allowIsolated, int32_t dumpPriority) override;
     binder::Status listServices(int32_t dumpPriority, std::vector<std::string>* outList) override;
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index e620770..7ad84fa 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -204,6 +204,11 @@
     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) {
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 64ef827..cd8ca89 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -305,6 +305,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.telephony.satellite.prebuilt.xml",
+    src: "android.hardware.telephony.satellite.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.telephony.ims.singlereg.prebuilt.xml",
     src: "android.hardware.telephony.ims.singlereg.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 9ea6549..52dbb61 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -29,7 +29,7 @@
  * workloads are taking. The framework will then compare the actual durations to the target
  * duration and attempt to help the client reach a steady state under the target.
  *
- * Unlike reportActualWorkDuration, the "notify..." hints are intended to be sent in
+ * Unlike reportActualWorkDuration, the "notifyWorkload..." hints are intended to be sent in
  * advance of large changes in the workload, to prevent them from going over the target
  * when there is a sudden, unforseen change. Their effects are intended to last for only
  * one cycle, after which reportActualWorkDuration will have a chance to catch up.
@@ -71,6 +71,10 @@
 #include <stdint.h>
 #include <unistd.h>
 
+#if !defined(__DEPRECATED_IN)
+#define __DEPRECATED_IN(__api_level, ...) __attribute__((__deprecated__))
+#endif
+
 __BEGIN_DECLS
 
 struct APerformanceHintManager;
@@ -116,13 +120,13 @@
  * An opaque type representing a handle to a performance hint session creation configuration.
  * It is consumed by {@link APerformanceHint_createSessionUsingConfig}.
  *
- * A session creation config encapsulates the required information for a session.
- * Additionally, the caller can set various settings for the session,
- * to be passed during creation, streamlining the session setup process.
+ * A session creation config encapsulates the required information for creating a session. The only
+ * mandatory parameter is the set of TIDs, set using {@link ASessionCreationConfig_setTids}. Only
+ * parameters relevant to the session need to be set, and any unspecified functionality will be
+ * treated as unused on the session. Configurations without a valid set of TIDs, or which try to
+ * enable automatic timing without the graphics pipeline mode, are considered invalid.
  *
- * The caller may reuse this object and modify the settings in it
- * to create additional sessions.
- *
+ * The caller may reuse this object and modify the settings in it to create additional sessions.
  */
 typedef struct ASessionCreationConfig ASessionCreationConfig;
 
@@ -181,27 +185,43 @@
         int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
- * Creates a session for the given set of threads that are graphics pipeline threads
- * and set their initial target work duration.
+ * Creates a session using arguments from a corresponding {@link ASessionCreationConfig}.
+ *
+ * Note: when using graphics pipeline mode, using too many cumulative graphics pipeline threads is
+ * not a failure and will still create a session, but it will cause all graphics pipeline sessions
+ * to have undefined behavior and the method will return EBUSY.
  *
  * @param manager The performance hint manager instance.
  * @param config The configuration struct containing required information
  *        to create a session.
- * @return APerformanceHintSession pointer on success, nullptr on failure.
+ * @param sessionOut A client-provided pointer, which will be set to the new APerformanceHintSession
+ *        on success or EBUSY, and to nullptr on failure.
+ *
+ * @return 0 on success.
+ *         EINVAL if the creation config is in an invalid state.
+ *         EPIPE if communication failed.
+ *         ENOTSUP if hint sessions are not supported, or if auto timing is enabled but unsupported.
+ *         EBUSY if too many graphics pipeline threads are passed.
  */
-APerformanceHintSession* _Nullable APerformanceHint_createSessionUsingConfig(
+int APerformanceHint_createSessionUsingConfig(
         APerformanceHintManager* _Nonnull manager,
-        ASessionCreationConfig* _Nonnull config)
-        __INTRODUCED_IN(36);
+        ASessionCreationConfig* _Nonnull config,
+        APerformanceHintSession * _Nullable * _Nonnull sessionOut) __INTRODUCED_IN(36);
 
 /**
  * Get preferred update rate information for this device.
  *
+ * @deprecated Client side rate limiting is not necessary, rate limiting is handled in the
+ *             framework. If you were using this to check for hint session support, please use
+ *             {@link APerformanceHint_isFeatureSupported} instead.
+ *
  * @param manager The performance hint manager instance.
  * @return the preferred update rate supported by device software.
  */
 int64_t APerformanceHint_getPreferredUpdateRateNanos(
-        APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintManager* _Nonnull manager)
+        __INTRODUCED_IN(__ANDROID_API_T__) __DEPRECATED_IN(36, "Client-side rate limiting is not"
+        " necessary, use APerformanceHint_isFeatureSupported for support checking.");
 
 /**
  * Get maximum number of graphics pipieline threads per-app for this device.
@@ -216,9 +236,11 @@
  * Updates this session's target duration for each cycle of work.
  *
  * @param session The performance hint session instance to update.
- * @param targetDurationNanos The new desired duration in nanoseconds. This must be positive.
+ * @param targetDurationNanos The new desired duration in nanoseconds. This must be positive for the
+ *        session to report work durations, and may be zero to disable this functionality.
+ *
  * @return 0 on success.
- *         EINVAL if targetDurationNanos is not positive.
+ *         EINVAL if targetDurationNanos is less than zero.
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_updateTargetWorkDuration(
@@ -235,7 +257,7 @@
  * @param actualDurationNanos The duration of time the thread group took to complete its last
  *     task in nanoseconds. This must be positive.
  * @return 0 on success.
- *         EINVAL if actualDurationNanos is not positive.
+ *         EINVAL if actualDurationNanos is not positive or the target it not positive.
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_reportActualWorkDuration(
@@ -258,15 +280,20 @@
  * Set a list of threads to the performance hint session. This operation will replace
  * the current list of threads with the given list of threads.
  *
+ * Note: when using a session with the graphics pipeline mode enabled, using too many cumulative
+ * graphics pipeline threads is not a failure, but it will cause all graphics pipeline sessions to
+ * have undefined behavior and the method will return EBUSY.
+ *
  * @param session The performance hint session instance to update.
  * @param threadIds The list of threads to be associated with this session. They must be part of
  *     this app's thread group.
  * @param size The size of the list of threadIds.
  * @return 0 on success.
  *         EINVAL if the list of thread ids is empty or if any of the thread ids are not part of
-               the thread group.
+ *         the thread group.
  *         EPIPE if communication with the system service has failed.
  *         EPERM if any thread id doesn't belong to the application.
+ *         EBUSY if too many graphics pipeline threads were passed.
  */
 int APerformanceHint_setThreads(
         APerformanceHintSession* _Nonnull session,
@@ -311,89 +338,102 @@
         AWorkDuration* _Nonnull workDuration) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
- * Informs the framework of an upcoming increase in the workload of a graphics pipeline
- * bound to this session. The user can specify whether the increase is expected to be
- * on the CPU, GPU, or both.
+ * Informs the framework of an upcoming increase in the workload of this session.
+ * The user can specify whether the increase is expected to be on the CPU, GPU, or both.
  *
- * Sending hints for both CPU and GPU counts as two separate hints for the purposes of the
- * rate limiter.
+ * These hints should be sent shortly before the start of the cycle where the workload is going to
+ * change, or as early as possible during that cycle for maximum effect. Hints sent towards the end
+ * of the cycle may be interpreted as applying to the next cycle. Any unsupported hints will be
+ * silently dropped, to avoid the need for excessive support checking each time they are sent, and
+ * sending a hint for both CPU and GPU will count as two separate hints for the rate limiter. These
+ * hints should not be sent repeatedly for an ongoing expensive workload, as workload time reporting
+ * is intended to handle this.
  *
+ * @param session The {@link APerformanceHintSession} instance to send a hint for.
  * @param cpu Indicates if the workload increase is expected to affect the CPU.
  * @param gpu Indicates if the workload increase is expected to affect the GPU.
- * @param debugName A required string used to identify this specific hint during
- *        tracing. This debug string will only be held for the duration of the
- *        method, and can be safely discarded after.
+ * @param identifier A required string used to distinguish this specific hint, using utf-8 encoding.
+ *        This string will only be held for the duration of the method, and can be discarded after.
  *
  * @return 0 on success.
- *         EINVAL if no hints were requested.
  *         EBUSY if the hint was rate limited.
  *         EPIPE if communication with the system service has failed.
- *         ENOTSUP if the hint is not supported.
  */
 int APerformanceHint_notifyWorkloadIncrease(
         APerformanceHintSession* _Nonnull session,
-        bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
+        bool cpu, bool gpu, const char* _Nonnull identifier) __INTRODUCED_IN(36);
 
 /**
- * Informs the framework of an upcoming reset in the workload of a graphics pipeline
- * bound to this session, or the imminent start of a new workload. The user can specify
- * whether the reset is expected to affect the CPU, GPU, or both.
+ * Informs the framework that the workload associated with this session is about to start, or that
+ * it is about to completely change, and that the system should discard any assumptions about its
+ * characteristics inferred from previous activity. The user can specify whether the reset is
+ * expected to affect the CPU, GPU, or both.
  *
- * Sending hints for both CPU and GPU counts as two separate hints for the purposes of the
- * this load tracking.
+ * These hints should be sent shortly before the start of the cycle where the workload is going to
+ * change, or as early as possible during that cycle for maximum effect. Hints sent towards the end
+ * of the cycle may be interpreted as applying to the next cycle. Any unsupported hints will be
+ * silently dropped, to avoid the need for excessive support checking each time they are sent, and
+ * sending a hint for both CPU and GPU will count as two separate hints for the rate limiter. These
+ * hints should not be sent repeatedly for an ongoing expensive workload, as workload time reporting
+ * is intended to handle this.
  *
+ * @param session The {@link APerformanceHintSession} instance to send a hint for.
  * @param cpu Indicates if the workload reset is expected to affect the CPU.
  * @param gpu Indicates if the workload reset is expected to affect the GPU.
- * @param debugName A required string used to identify this specific hint during
- *        tracing. This debug string will only be held for the duration of the
- *        method, and can be safely discarded after.
+ * @param identifier A required string used to distinguish this specific hint, using utf-8 encoding.
+ *        This string will only be held for the duration of the method, and can be discarded after.
  *
  * @return 0 on success.
- *         EINVAL if no hints were requested.
  *         EBUSY if the hint was rate limited.
  *         EPIPE if communication with the system service has failed.
- *         ENOTSUP if the hint is not supported.
  */
 int APerformanceHint_notifyWorkloadReset(
         APerformanceHintSession* _Nonnull session,
-        bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
+        bool cpu, bool gpu, const char* _Nonnull identifier) __INTRODUCED_IN(36);
 
 /**
- * Informs the framework of an upcoming one-off expensive frame for a graphics pipeline
- * bound to this session. This frame will be treated as not representative of the workload as a
- * whole, and it will be discarded the purposes of load tracking. The user can specify
- * whether the workload spike is expected to be on the CPU, GPU, or both.
+ * Informs the framework of an upcoming one-off expensive workload cycle for a given session.
+ * This cycle will be treated as not representative of the workload as a whole, and it will be
+ * discarded the purposes of load tracking. The user can specify whether the workload spike is
+ * expected to be on the CPU, GPU, or both.
  *
- * Sending hints for both CPU and GPU counts as two separate hints for the purposes of the
- * rate limiter.
+ * These hints should be sent shortly before the start of the cycle where the workload is going to
+ * change, or as early as possible during that cycle for maximum effect. Hints sent towards the end
+ * of the cycle may be interpreted as applying to the next cycle. Any unsupported hints will be
+ * silently dropped, to avoid the need for excessive support checking each time they are sent, and
+ * sending a hint for both CPU and GPU will count as two separate hints for the rate limiter. These
+ * hints should not be sent repeatedly for an ongoing expensive workload, as workload time reporting
+ * is intended to handle this.
  *
+ * @param session The {@link APerformanceHintSession} instance to send a hint for.
  * @param cpu Indicates if the workload spike is expected to affect the CPU.
  * @param gpu Indicates if the workload spike is expected to affect the GPU.
- * @param debugName A required string used to identify this specific hint during
- *        tracing. This debug string will only be held for the duration of the
- *        method, and can be safely discarded after.
+ * @param identifier A required string used to distinguish this specific hint, using utf-8 encoding.
+ *        This string will only be held for the duration of the method, and can be discarded after.
  *
  * @return 0 on success.
- *         EINVAL if no hints were requested.
  *         EBUSY if the hint was rate limited.
  *         EPIPE if communication with the system service has failed.
- *         ENOTSUP if the hint is not supported.
  */
 int APerformanceHint_notifyWorkloadSpike(
         APerformanceHintSession* _Nonnull session,
-        bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
+        bool cpu, bool gpu, const char* _Nonnull identifier) __INTRODUCED_IN(36);
 
 /**
  * Associates a session with any {@link ASurfaceControl} or {@link ANativeWindow}
- * instances managed by this session.
+ * instances managed by this session. Any previously associated objects that are not passed
+ * in again lose their association. Invalid or dead instances are ignored, and passing both
+ * lists as null drops all current associations.
  *
  * This method is primarily intended for sessions that manage the timing of an entire
- * graphics pipeline end-to-end, such as those using the
+ * graphics pipeline end-to-end for frame pacing, such as those using the
  * {@link ASessionCreationConfig_setGraphicsPipeline} API. However, any session directly
  * or indirectly managing a graphics pipeline should still associate themselves with
  * directly relevant ASurfaceControl or ANativeWindow instances for better optimization.
+ * Additionally, if the surface associated with a session changes, this method should be called
+ * again to re-create the association.
  *
- * To see any benefit from this method, the client must make sure they are updating the framerate
+ * To see any benefit from this method, the client must make sure they are updating the frame rate
  * of attached surfaces using methods such as {@link ANativeWindow_setFrameRate}, or by updating
  * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
  *
@@ -407,16 +447,77 @@
  *
  * @return 0 on success.
  *         EPIPE if communication has failed.
- *         ENOTSUP if unsupported.
- *         EINVAL if invalid or empty arguments passed.
+ *         ENOTSUP if this is not supported on the device.
  */
 
 int APerformanceHint_setNativeSurfaces(APerformanceHintSession* _Nonnull session,
-        ANativeWindow* _Nonnull* _Nullable nativeWindows, int nativeWindowsSize,
-        ASurfaceControl* _Nonnull* _Nullable surfaceControls, int surfaceControlsSize)
+        ANativeWindow* _Nonnull* _Nullable nativeWindows, size_t nativeWindowsSize,
+        ASurfaceControl* _Nonnull* _Nullable surfaceControls, size_t surfaceControlsSize)
         __INTRODUCED_IN(36);
 
 /**
+ * This enum represents different aspects of performance hint functionality. These can be passed
+ * to {@link APerformanceHint_isFeatureSupported} to determine whether the device exposes support
+ * for that feature.
+ *
+ * Some of these features will not expose failure to the client if used when unsupported, to prevent
+ * the client from needing to worry about handling different logic for each possible support
+ * configuration. The exception to this is features with important user-facing side effects, such as
+ * {@link APERF_HINT_AUTO_CPU} and {@link APERF_HINT_AUTO_GPU} modes which expect the client not to
+ * report durations while they are active.
+ */
+typedef enum APerformanceHintFeature : int32_t {
+    /**
+     * This value represents all APerformanceHintSession functionality. Using the Performance Hint
+     * API at all if this is not enabled will likely result in either
+     * {@link APerformanceHintManager} or {@link APerformanceHintSession} failing to create, or the
+     * session having little to no benefit even if creation succeeds.
+     */
+    APERF_HINT_SESSIONS,
+
+    /**
+     * This value represents the power efficiency mode, as exposed by
+     * {@link ASessionCreationConfig_setPreferPowerEfficiency} and
+     * {@link APerformanceHint_setPreferPowerEfficiency}.
+     */
+    APERF_HINT_POWER_EFFICIENCY,
+
+    /**
+     * This value the ability for sessions to bind to surfaces using
+     * {@link APerformanceHint_setNativeSurfaces} or
+     * {@link ASessionCreationConfig_setNativeSurfaces}
+     */
+    APERF_HINT_SURFACE_BINDING,
+
+    /**
+     * This value represents the "graphics pipeline" mode, as exposed by
+     * {@link ASessionCreationConfig_setGraphicsPipeline}.
+     */
+    APERF_HINT_GRAPHICS_PIPELINE,
+
+    /**
+     * This value represents the automatic CPU timing feature, as exposed by
+     * {@link ASessionCreationConfig_setUseAutoTiming}.
+     */
+    APERF_HINT_AUTO_CPU,
+
+    /**
+     * This value represents the automatic GPU timing feature, as exposed by
+     * {@link ASessionCreationConfig_setUseAutoTiming}.
+     */
+    APERF_HINT_AUTO_GPU,
+} APerformanceHintFeature;
+
+/**
+ * Checks whether the device exposes support for a specific feature.
+ *
+ * @param feature The specific feature enum to check.
+ *
+ * @return false if unsupported, true if supported.
+ */
+bool APerformanceHint_isFeatureSupported(APerformanceHintFeature feature) __INTRODUCED_IN(36);
+
+/**
  * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
  * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
  * associated with it.
@@ -507,7 +608,6 @@
 ASessionCreationConfig* _Nonnull ASessionCreationConfig_create()
                 __INTRODUCED_IN(36);
 
-
 /**
  * Destroys a {@link ASessionCreationConfig} and frees all
  * resources associated with it.
@@ -526,11 +626,8 @@
  * @param tids The list of tids to be associated with this session. They must be part of
  *        this process' thread group.
  * @param size The size of the list of tids.
- *
- * @return 0 on success.
- *         EINVAL if invalid array pointer or the value of size
  */
-int ASessionCreationConfig_setTids(
+void ASessionCreationConfig_setTids(
         ASessionCreationConfig* _Nonnull config,
         const pid_t* _Nonnull tids, size_t size)  __INTRODUCED_IN(36);
 
@@ -539,15 +636,11 @@
  *
  * @param config The {@link ASessionCreationConfig}
  *        created by calling {@link ASessionCreationConfig_create()}.
- * @param targetWorkDurationNanos The parameter to specify a target duration
- *        in nanoseconds for the new session; this value must be positive to use
- *        the work duration API.
- *
- * @return 0 on success.
- *         ENOTSUP if unsupported
- *         EINVAL if invalid value
+ * @param targetWorkDurationNanos The parameter to specify a target duration in nanoseconds for the
+ *        new session; this value must be positive to use the work duration API, and may be ignored
+ *        otherwise or set to zero. Negative values are invalid.
  */
-int ASessionCreationConfig_setTargetWorkDurationNanos(
+void ASessionCreationConfig_setTargetWorkDurationNanos(
         ASessionCreationConfig* _Nonnull config,
         int64_t targetWorkDurationNanos)  __INTRODUCED_IN(36);
 
@@ -559,12 +652,8 @@
  * @param config The {@link ASessionCreationConfig}
  *        created by calling {@link ASessionCreationConfig_create()}.
  * @param enabled Whether power efficiency mode will be enabled.
- *
- * @return 0 on success.
- *         ENOTSUP if unsupported
- *         EINVAL if invalid pointer to creation config
  */
-int ASessionCreationConfig_setPreferPowerEfficiency(
+void ASessionCreationConfig_setPreferPowerEfficiency(
         ASessionCreationConfig* _Nonnull config, bool enabled)  __INTRODUCED_IN(36);
 
 /**
@@ -574,24 +663,31 @@
  * buffer is fully finished drawing.
  *
  * It should include any threads on the critical path of that pipeline,
- * up to a limit accessible from {@link getMaxGraphicsPipelineThreadsCount()}.
+ * up to a limit accessible from {@link APerformanceHint_getMaxGraphicsPipelineThreadsCount()}.
  *
  * @param config The {@link ASessionCreationConfig}
  *        created by calling {@link ASessionCreationConfig_create()}.
  * @param enabled Whether this session manages a graphics pipeline's critical path.
- *
- * @return 0 on success.
- *         ENOTSUP if unsupported
- *         EINVAL if invalid pointer to creation config or maximum threads for graphics
-                  pipeline is reached.
  */
-int ASessionCreationConfig_setGraphicsPipeline(
+void ASessionCreationConfig_setGraphicsPipeline(
         ASessionCreationConfig* _Nonnull config, bool enabled)  __INTRODUCED_IN(36);
 
 /**
- * Associates a session with any {@link ASurfaceControl} or {@link ANativeWindow}
- * instances managed by this session. See {@link APerformanceHint_setNativeSurfaces}
- * for more details.
+ * Associates the created session with any {@link ASurfaceControl} or {@link ANativeWindow}
+ * instances it will be managing. Invalid or dead instances are ignored.
+ *
+ * This method is primarily intended for sessions that manage the timing of an entire
+ * graphics pipeline end-to-end for frame pacing, such as those using the
+ * {@link ASessionCreationConfig_setGraphicsPipeline} API. However, any session directly
+ * or indirectly managing a graphics pipeline should still associate themselves with
+ * directly relevant ASurfaceControl or ANativeWindow instances for better optimization.
+ * Additionally, if the surface associated with a session changes, this method should be called
+ * again to re-create the association.
+ *
+ * To see any benefit from this method, the client must make sure they are updating the frame rate
+ * of attached surfaces using methods such as {@link ANativeWindow_setFrameRate}, or by updating
+ * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
+ *
  *
  * @param config The {@link ASessionCreationConfig}
  *        created by calling {@link ASessionCreationConfig_create()}.
@@ -601,49 +697,46 @@
  * @param surfaceControls A pointer to a list of ASurfaceControls associated with this session.
  *        nullptr can be passed to indicate there are no associated ASurfaceControls.
  * @param surfaceControlsSize The number of ASurfaceControls in the list.
- *
- * @return 0 on success.
- *         ENOTSUP if unsupported.
- *         EINVAL if invalid or empty arguments passed.
  */
-int ASessionCreationConfig_setNativeSurfaces(
+void ASessionCreationConfig_setNativeSurfaces(
         ASessionCreationConfig* _Nonnull config,
-        ANativeWindow* _Nonnull* _Nullable nativeWindows, int nativeWindowsSize,
-        ASurfaceControl* _Nonnull* _Nullable surfaceControls, int surfaceControlsSize)
+        ANativeWindow* _Nonnull* _Nullable nativeWindows, size_t nativeWindowsSize,
+        ASurfaceControl* _Nonnull* _Nullable surfaceControls, size_t surfaceControlsSize)
         __INTRODUCED_IN(36);
 
 /**
  * Enable automatic timing mode for sessions using the GRAPHICS_PIPELINE API with an attached
- * surface. In this mode, sessions do not need to report actual durations and only need
- * to keep their thread list up-to-date, set a native surface, call
- * {@link ASessionCreationConfig_setGraphicsPipeline()} to signal that the session is in
- * "graphics pipeline" mode, and then set whether automatic timing is desired for the
- * CPU, GPU, or both, using this method.
+ * surface. In this mode, sessions do not need to report timing data for the CPU, GPU, or both
+ * depending on the configuration. To use this mode, sessions should set a native surface
+ * using {@ASessionCreationConfig_setNativeSurfaces}, enable graphics pipeline mode with
+ * {@link ASessionCreationConfig_setGraphicsPipeline()}, and then call this method to set whether
+ * automatic timing is desired for the CPU, GPU, or both. Trying to enable this without also
+ * enabling the graphics pipeline mode will cause session creation to fail.
  *
  * It is still be beneficial to set an accurate target time, as this may help determine
  * timing information for some workloads where there is less information available from
  * the framework, such as games. Additionally, reported CPU durations will be ignored
  * while automatic CPU timing is enabled, and similarly GPU durations will be ignored
  * when automatic GPU timing is enabled. When both are enabled, the entire
- * reportActualWorkDuration call will be ignored, and the session will be managed
- * completely automatically.
+ * {@link APerformanceHint_reportActualWorkDuration} call will be ignored, and the session will be
+ * managed completely automatically.
  *
- * This mode will not work unless the client makes sure they are updating the framerate
- * of attached surfaces with methods such as {@link ANativeWindow_setFrameRate}, or updating
- * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
+ * If the client is manually controlling their frame rate for those surfaces, then they must make
+ * sure they are updating the frame rate with {@link ANativeWindow_setFrameRate}, or updating any
+ * associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate} set.
+ *
+ * The user of this API should ensure this feature is supported by checking
+ * {@link APERF_HINT_AUTO_CPU} and {@link APERF_HINT_AUTO_GPU} with
+ * {@link APerformanceHint_isFeatureSupported} and falling back to manual timing if it is not.
+ * Trying to use automatic timing when it is unsupported will cause session creation to fail.
  *
  * @param config The {@link ASessionCreationConfig}
  *        created by calling {@link ASessionCreationConfig_create()}.
  * @param cpu Whether to enable automatic timing for the CPU for this session.
  * @param gpu Whether to enable automatic timing for the GPU for this session.
- *
- * @return 0 on success.
- *         ENOTSUP if unsupported.
  */
-int ASessionCreationConfig_setUseAutoTiming(
-        ASessionCreationConfig* _Nonnull config,
-        bool cpu, bool gpu)
-        __INTRODUCED_IN(36);
+void ASessionCreationConfig_setUseAutoTiming(
+        ASessionCreationConfig* _Nonnull config, bool cpu, bool gpu) __INTRODUCED_IN(36);
 
 __END_DECLS
 
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
index f0503f6..59d3a31 100644
--- a/include/android/surface_control_input_receiver.h
+++ b/include/android/surface_control_input_receiver.h
@@ -39,11 +39,11 @@
  *
  * \param motionEvent The motion event. This must be released with AInputEvent_release.
  *
+ * \return true if the event is handled by the client, false otherwise.
  * Available since API level 35.
  */
 typedef bool (*AInputReceiver_onMotionEvent)(void *_Null_unspecified context,
-                                             AInputEvent *_Nonnull motionEvent)
-                                            __INTRODUCED_IN(__ANDROID_API_V__);
+                                             AInputEvent *_Nonnull motionEvent);
 /**
  * The AInputReceiver_onKeyEvent callback is invoked when the registered input channel receives
  * a key event.
@@ -53,11 +53,12 @@
  *
  * \param keyEvent The key event. This must be released with AInputEvent_release.
  *
+ * \return true if the event is handled by the client, false otherwise. System may generate
+ * a fallback key event if the event is not handled.
  * Available since API level 35.
  */
 typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context,
-                                          AInputEvent *_Nonnull keyEvent)
-                                          __INTRODUCED_IN(__ANDROID_API_V__);
+                                          AInputEvent *_Nonnull keyEvent);
 
 typedef struct AInputReceiverCallbacks AInputReceiverCallbacks;
 
diff --git a/include/android/system_health.h b/include/android/system_health.h
index 69620df..bdb1413 100644
--- a/include/android/system_health.h
+++ b/include/android/system_health.h
@@ -16,6 +16,31 @@
 
 /**
 * @defgroup SystemHealth
+*
+ * SystemHealth provides access to data about how various system resources are used by applications.
+ *
+ * CPU/GPU headroom APIs are designed to be best used by applications with consistent and intense
+ * workload such as games to query the remaining capacity headroom over a short period and perform
+ * optimization accordingly. Due to the nature of the fast job scheduling and frequency scaling of
+ * CPU and GPU, the headroom by nature will have "TOCTOU" problem which makes it less suitable for
+ * apps with inconsistent or low workload to take any useful action but simply monitoring. And to
+ * avoid oscillation it's not recommended to adjust workload too frequent (on each polling request)
+ * or too aggressively. As the headroom calculation is more based on reflecting past history usage
+ * than predicting future capacity. Take game as an example, if the API returns CPU headroom of 0 in
+ * one scenario (especially if it's constant across multiple calls), or some value significantly
+ * smaller than other scenarios, then it can reason that the recent performance result is more CPU
+ * bottlenecked. Then reducing the CPU workload intensity can help reserve some headroom to handle
+ * the load variance better, which can result in less frame drops or smooth FPS value. On the other
+ * hand, if the API returns large CPU headroom constantly, the app can be more confident to increase
+ * the workload and expect higher possibility of device meeting its performance expectation.
+ * App can also use thermal APIs to read the current thermal status and headroom first, then poll
+ * the CPU and GPU headroom if the device is (about to) getting thermal throttled. If the CPU/GPU
+ * headrooms provide enough significance such as one valued at 0 while the other at 100, then it can
+ * be used to infer that reducing CPU workload could be more efficient to cool down the device.
+ * There is a caveat that the power controller may scale down the frequency of the CPU and GPU due
+ * to thermal and other reasons, which can result in a higher than usual percentage usage of the
+ * capacity.
+ *
 * @{
 */
 
@@ -70,55 +95,40 @@
  */
 typedef struct AGpuHeadroomParams AGpuHeadroomParams;
 
-/**
- * Creates a new instance of ACpuHeadroomParams.
- *
- * When the client finishes using {@link ACpuHeadroomParams},
- * {@link ACpuHeadroomParams_destroy()} must be called to destroy
- * and free up the resources associated with {@link ACpuHeadroomParams}.
- *
- * Available since API level 36.
- *
- * @return A new instance of ACpuHeadroomParams.
- */
-ACpuHeadroomParams *_Nonnull ACpuHeadroomParams_create()
-__INTRODUCED_IN(36);
-
-enum ACpuHeadroomCalculationType {
+typedef enum ACpuHeadroomCalculationType : int32_t {
     /**
-     * Use the minimum headroom value within the calculation window.
+     * The headroom calculation type bases on minimum value over a specified window.
      * Introduced in API level 36.
      */
     ACPU_HEADROOM_CALCULATION_TYPE_MIN = 0,
     /**
-     * Use the average headroom value within the calculation window.
+     * The headroom calculation type bases on average value over a specified window.
      * Introduced in API level 36.
      */
     ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
-};
-typedef enum ACpuHeadroomCalculationType ACpuHeadroomCalculationType;
+} ACpuHeadroomCalculationType;
 
-enum AGpuHeadroomCalculationType {
+typedef enum AGpuHeadroomCalculationType : int32_t {
     /**
-     * Use the minimum headroom value within the calculation window.
+     * The headroom calculation type bases on minimum value over a specified window.
      * Introduced in API level 36.
      */
     AGPU_HEADROOM_CALCULATION_TYPE_MIN = 0,
     /**
-     * Use the average headroom value within the calculation window.
+     * The headroom calculation type bases on average value over a specified window.
      * Introduced in API level 36.
      */
     AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
-};
-typedef enum AGpuHeadroomCalculationType AGpuHeadroomCalculationType;
+} AGpuHeadroomCalculationType;
 
 /**
- * Sets the headroom calculation window size in ACpuHeadroomParams.
+ * Sets the CPU headroom calculation window size in milliseconds.
  *
  * Available since API level 36.
  *
  * @param params The params to be set.
- * @param windowMillis The window size in milliseconds ranged from [50, 10000]. The smaller the
+ * @param windowMillis The window size in milliseconds ranges from
+ *                     {@link ASystemHealth_getCpuHeadroomCalculationWindowRange}. The smaller the
  *                     window size, the larger fluctuation in the headroom value should be expected.
  *                     The default value can be retrieved from the
  *                     {@link #ACpuHeadroomParams_getCalculationWindowMillis} if not set. The device
@@ -129,136 +139,210 @@
 __INTRODUCED_IN(36);
 
 /**
- * Gets the headroom calculation window size in ACpuHeadroomParams.
+ * Gets the CPU headroom calculation window size in milliseconds.
+ *
+ * This will return the default value chosen by the device if not set.
  *
  * Available since API level 36.
  *
- * @param params The params to be set.
+ * @param params The params to read from.
  * @return This will return the default value chosen by the device if the params is not set.
  */
-int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params)
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
- * Sets the headroom calculation window size in AGpuHeadroomParams.
+ * Sets the GPU headroom calculation window size in milliseconds.
  *
  * Available since API level 36.
  *
  * @param params The params to be set.
- * @param windowMillis The window size in milliseconds ranged from [50, 10000]. The smaller the
+ * @param windowMillis The window size in milliseconds ranges from
+ *                     {@link ASystemHealth_getGpuHeadroomCalculationWindowRange}. The smaller the
  *                     window size, the larger fluctuation in the headroom value should be expected.
  *                     The default value can be retrieved from the
  *                     {@link #AGpuHeadroomParams_getCalculationWindowMillis} if not set. The device
  *                     will try to use the closest feasible window size to this param.
  */
-void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params,
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
                                                    int windowMillis)
 __INTRODUCED_IN(36);
 
 /**
- * Gets the headroom calculation window size in AGpuHeadroomParams.
+ * Gets the GPU headroom calculation window size in milliseconds.
+ *
+ * This will return the default value chosen by the device if not set.
  *
  * Available since API level 36.
  *
- * @param params The params to be set.
+ * @param params The params to read from.
  * @return This will return the default value chosen by the device if the params is not set.
  */
-int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params)
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
- * Sets the headroom calculation type in ACpuHeadroomParams.
+ * Sets the CPU headroom calculation type in {@link ACpuHeadroomParams}.
  *
  * Available since API level 36.
  *
  * @param params The params to be set.
  * @param calculationType The headroom calculation type.
  */
-void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams *_Nonnull params,
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
                                            ACpuHeadroomCalculationType calculationType)
 __INTRODUCED_IN(36);
 
 /**
- * Gets the headroom calculation type in ACpuHeadroomParams.
+ * Gets the CPU headroom calculation type in {@link ACpuHeadroomParams}.
+ *
+ * This will return the default value chosen by the device if not set.
  *
  * Available since API level 36.
  *
- * @param params The params to be set.
+ * @param params The params to read from.
  * @return The headroom calculation type.
  */
 ACpuHeadroomCalculationType
-ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams *_Nonnull params)
+ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
- * Sets the headroom calculation type in AGpuHeadroomParams.
+ * Sets the GPU headroom calculation type in {@link AGpuHeadroomParams}.
  *
  * Available since API level 36.
  *
  * @param params The params to be set.
  * @param calculationType The headroom calculation type.
  */
-void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams *_Nonnull params,
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
                                            AGpuHeadroomCalculationType calculationType)
 __INTRODUCED_IN(36);
 
 /**
- * Gets the headroom calculation type in AGpuHeadroomParams.
+ * Gets the GPU headroom calculation type in {@link AGpuHeadroomParams}.
+ *
+ * This will return the default value chosen by the device if not set.
  *
  * Available since API level 36.
  *
- * @param params The params to be set.
+ * @param params The params to read from.
  * @return The headroom calculation type.
  */
 AGpuHeadroomCalculationType
-AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams *_Nonnull params)
+AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
- * Sets the thread TIDs to track in ACpuHeadroomParams.
+ * Sets the thread TIDs to track in {@link ACpuHeadroomParams}.
+ *
+ * The TIDs should belong to the same of the process that will make the headroom call. And they
+ * should not have different core affinity.
+ *
+ * If not set or set to empty, the headroom will be based on the PID of the process making the call.
  *
  * Available since API level 36.
  *
  * @param params The params to be set.
- * @param tids Non-null array of TIDs, maximum 5.
+ * @param tids Non-null array of TIDs, where maximum size can be read from
+ *             {@link ASystemHealth_getMaxCpuHeadroomTidsSize}.
  * @param tidsSize The size of the tids array.
  */
-void ACpuHeadroomParams_setTids(ACpuHeadroomParams *_Nonnull params, const int *_Nonnull tids,
-                                int tidsSize)
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
+                                size_t tidsSize)
 __INTRODUCED_IN(36);
 
 /**
- * Creates a new instance of AGpuHeadroomParams.
+ * Creates a new instance of {@link ACpuHeadroomParams}.
+ *
+ * When the client finishes using {@link ACpuHeadroomParams},
+ * {@link ACpuHeadroomParams_destroy} must be called to destroy
+ * and free up the resources associated with {@link ACpuHeadroomParams}.
+ *
+ * Available since API level 36.
+ *
+ * @return A new instance of {@link ACpuHeadroomParams}.
+ */
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create(void)
+__INTRODUCED_IN(36);
+
+/**
+ * Creates a new instance of {@link AGpuHeadroomParams}.
  *
  * When the client finishes using {@link AGpuHeadroomParams},
- * {@link AGpuHeadroomParams_destroy()} must be called to destroy
+ * {@link AGpuHeadroomParams_destroy} must be called to destroy
  * and free up the resources associated with {@link AGpuHeadroomParams}.
  *
  * Available since API level 36.
  *
- * @return A new instance of AGpuHeadroomParams.
+ * @return A new instance of {@link AGpuHeadroomParams}.
  */
-AGpuHeadroomParams *_Nonnull AGpuHeadroomParams_create()
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create(void)
 __INTRODUCED_IN(36);
 
 /**
- * Deletes the ACpuHeadroomParams instance.
+ * Deletes the {@link ACpuHeadroomParams} instance.
  *
  * Available since API level 36.
  *
  * @param params The params to be deleted.
  */
-void ACpuHeadroomParams_destroy(ACpuHeadroomParams *_Nonnull params)
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nullable params)
 __INTRODUCED_IN(36);
 
 /**
- * Deletes the AGpuHeadroomParams instance.
+ * Deletes the {@link AGpuHeadroomParams} instance.
  *
  * Available since API level 36.
  *
  * @param params The params to be deleted.
  */
-void AGpuHeadroomParams_destroy(AGpuHeadroomParams *_Nonnull params)
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nullable params)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets the maximum number of TIDs this device supports for getting CPU headroom.
+ *
+ * See {@link ACpuHeadroomParams_setTids}.
+ *
+ * Available since API level 36.
+ *
+ * @param outSize Non-null output pointer to the max size.
+ * @return 0 on success.
+ *         ENOTSUP if the CPU headroom API is unsupported.
+ */
+int ASystemHealth_getMaxCpuHeadroomTidsSize(size_t* _Nonnull outSize);
+
+/**
+ * Gets the range of the calculation window size for CPU headroom.
+ *
+ * In API version 36, the range will be a superset of [50, 10000].
+ *
+ * Available since API level 36.
+ *
+ * @param outMinMillis Non-null output pointer to be set to the minimum window size in milliseconds.
+ * @param outMaxMillis Non-null output pointer to be set to the maximum window size in milliseconds.
+ * @return 0 on success.
+ *         ENOTSUP if API is unsupported.
+ */
+int ASystemHealth_getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis,
+                                                       int32_t* _Nonnull outMaxMillis)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets the range of the calculation window size for GPU headroom.
+ *
+ * In API version 36, the range will be a superset of [50, 10000].
+ *
+ * Available since API level 36.
+ *
+ * @param outMinMillis Non-null output pointer to be set to the minimum window size in milliseconds.
+ * @param outMaxMillis Non-null output pointer to be set to the maximum window size in milliseconds.
+ * @return 0 on success.
+ *         ENOTSUP if API is unsupported.
+ */
+int ASystemHealth_getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis,
+                                                       int32_t* _Nonnull outMaxMillis)
 __INTRODUCED_IN(36);
 
 /**
@@ -269,21 +353,27 @@
  * be used with the thermal status and headroom to determine if reducing the CPU bound workload can
  * help reduce the device temperature to avoid thermal throttling.
  *
+ * If the params are valid, each call will perform at least one synchronous binder transaction that
+ * can take more than 1ms. So it's not recommended to call or wait for this on critical threads.
+ * Some devices may implement this as an on-demand API with lazy initialization, so the caller
+ * should expect higher latency when making the first call (especially with non-default params)
+ * since app starts or after changing params, as the device may need to change its data collection.
+ *
  * Available since API level 36.
  *
- * @param params The params to customize the CPU headroom calculation, or nullptr to use the default
+ * @param params The params to customize the CPU headroom calculation, or nullptr to use default.
  * @param outHeadroom Non-null output pointer to a single float, which will be set to the CPU
  *                    headroom value. The value will be a single value or `Float.NaN` if it's
- *                    temporarily unavailable.
+ *                    temporarily unavailable due to server error or not enough user CPU workload.
  *                    Each valid value ranges from [0, 100], where 0 indicates no more cpu resources
  *                    can be granted.
- * @return 0 on success
+ * @return 0 on success.
  *         EPIPE if failed to get the CPU headroom.
  *         EPERM if the TIDs do not belong to the same process.
  *         ENOTSUP if API or requested params is unsupported.
  */
-int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams *_Nullable params,
-                                 float *_Nonnull outHeadroom)
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom)
 __INTRODUCED_IN(36);
 
 /**
@@ -294,52 +384,58 @@
  * be used with the thermal status and headroom to determine if reducing the GPU bound workload can
  * help reduce the device temperature to avoid thermal throttling.
  *
+ * If the params are valid, each call will perform at least one synchronous binder transaction that
+ * can take more than 1ms. So it's not recommended to call or wait for this on critical threads.
+ * Some devices may implement this as an on-demand API with lazy initialization, so the caller
+ * should expect higher latency when making the first call (especially with non-default params)
+ * since app starts or after changing params, as the device may need to change its data collection.
+ *
  * Available since API level 36
  *
- * @param params The params to customize the GPU headroom calculation, or nullptr to use the default
+ * @param params The params to customize the GPU headroom calculation, or nullptr to use default
  * @param outHeadroom Non-null output pointer to a single float, which will be set to the GPU
  *                    headroom value. The value will be a single value or `Float.NaN` if it's
  *                    temporarily unavailable.
  *                    Each valid value ranges from [0, 100], where 0 indicates no more gpu resources
  *                    can be granted.
- * @return 0 on success
+ * @return 0 on success.
  *         EPIPE if failed to get the GPU headroom.
  *         ENOTSUP if API or requested params is unsupported.
  */
-int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams *_Nullable params,
-                                 float *_Nonnull outHeadroom)
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom)
 __INTRODUCED_IN(36);
 
 /**
  * Gets minimum polling interval for calling {@link ASystemHealth_getCpuHeadroom} in milliseconds.
  *
- * The getCpuHeadroom API may return cached result if called more frequently than the interval.
+ * The {@link ASystemHealth_getCpuHeadroom} API may return cached result if called more frequently
+ * than the interval.
  *
  * Available since API level 36.
  *
  * @param outMinIntervalMillis Non-null output pointer to a int64_t, which
  *                will be set to the minimum polling interval in milliseconds.
- * @return 0 on success
- *         EPIPE if failed to get the minimum polling interval.
+ * @return 0 on success.
  *         ENOTSUP if API is unsupported.
  */
-int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis)
 __INTRODUCED_IN(36);
 
 /**
  * Gets minimum polling interval for calling {@link ASystemHealth_getGpuHeadroom} in milliseconds.
  *
- * The getGpuHeadroom API may return cached result if called more frequent than the interval.
+ * The {@link ASystemHealth_getGpuHeadroom} API may return cached result if called more frequently
+ * than the interval.
  *
  * Available since API level 36.
  *
  * @param outMinIntervalMillis Non-null output pointer to a int64_t, which
  *                will be set to the minimum polling interval in milliseconds.
- * @return 0 on success
- *         EPIPE if failed to get the minimum polling interval.
+ * @return 0 on success.
  *         ENOTSUP if API is unsupported.
  */
-int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis)
 __INTRODUCED_IN(36);
 
 #ifdef __cplusplus
diff --git a/include/audiomanager/IAudioManager.h b/include/audiomanager/IAudioManager.h
index a35a145..b0641b8 100644
--- a/include/audiomanager/IAudioManager.h
+++ b/include/audiomanager/IAudioManager.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_IAUDIOMANAGER_H
 #define ANDROID_IAUDIOMANAGER_H
 
+#include <android/media/IAudioManagerNative.h>
 #include <audiomanager/AudioManager.h>
 #include <utils/Errors.h>
 #include <binder/IInterface.h>
@@ -34,20 +35,23 @@
     // These transaction IDs must be kept in sync with the method order from
     // IAudioService.aidl.
     enum {
-        TRACK_PLAYER                          = IBinder::FIRST_CALL_TRANSACTION,
-        PLAYER_ATTRIBUTES                     = IBinder::FIRST_CALL_TRANSACTION + 1,
-        PLAYER_EVENT                          = IBinder::FIRST_CALL_TRANSACTION + 2,
-        RELEASE_PLAYER                        = IBinder::FIRST_CALL_TRANSACTION + 3,
-        TRACK_RECORDER                        = IBinder::FIRST_CALL_TRANSACTION + 4,
-        RECORDER_EVENT                        = IBinder::FIRST_CALL_TRANSACTION + 5,
-        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,
+        GET_NATIVE_INTERFACE                  = IBinder::FIRST_CALL_TRANSACTION,
+        TRACK_PLAYER                          = IBinder::FIRST_CALL_TRANSACTION + 1,
+        PLAYER_ATTRIBUTES                     = IBinder::FIRST_CALL_TRANSACTION + 2,
+        PLAYER_EVENT                          = IBinder::FIRST_CALL_TRANSACTION + 3,
+        RELEASE_PLAYER                        = IBinder::FIRST_CALL_TRANSACTION + 4,
+        TRACK_RECORDER                        = IBinder::FIRST_CALL_TRANSACTION + 5,
+        RECORDER_EVENT                        = IBinder::FIRST_CALL_TRANSACTION + 6,
+        RELEASE_RECORDER                      = IBinder::FIRST_CALL_TRANSACTION + 7,
+        PLAYER_SESSION_ID                     = IBinder::FIRST_CALL_TRANSACTION + 8,
+        PORT_EVENT                            = IBinder::FIRST_CALL_TRANSACTION + 9,
+        PERMISSION_UPDATE_BARRIER             = IBinder::FIRST_CALL_TRANSACTION + 10,
     };
 
     DECLARE_META_INTERFACE(AudioManager)
 
+    virtual sp<media::IAudioManagerNative> getNativeInterface() = 0;
+
     // The parcels created by these methods must be kept in sync with the
     // corresponding methods from IAudioService.aidl and objects it imports.
     virtual audio_unique_id_t trackPlayer(player_type_t playerType, audio_usage_t usage,
diff --git a/include/ftl/finalizer.h b/include/ftl/finalizer.h
new file mode 100644
index 0000000..0251957
--- /dev/null
+++ b/include/ftl/finalizer.h
@@ -0,0 +1,211 @@
+/*
+ * 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 <cstddef>
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/function.h>
+
+namespace android::ftl {
+
+// An RAII wrapper that invokes a function object as a finalizer when destroyed.
+//
+// The function object must take no arguments, and must return void. If the function object needs
+// any context for the call, it must store it itself, for example with a lambda capture.
+//
+// The stored function object will be called once (unless canceled via the `cancel()` member
+// function) at the first of:
+//
+//   - The Finalizer instance is destroyed.
+//   - `operator()` is used to invoke the contained function.
+//   - The Finalizer instance is move-assigned a new value. The function being replaced will be
+//     invoked, and the replacement will be stored to be called later.
+//
+// The intent with this class is to keep cleanup code next to the code that requires that
+// cleanup be performed.
+//
+//   bool read_file(std::string filename) {
+//     FILE* f = fopen(filename.c_str(), "rb");
+//     if (f == nullptr) return false;
+//     const auto cleanup = ftl::Finalizer([f]() { fclose(f); });
+//     // fread(...), etc
+//     return true;
+//   }
+//
+// The `FinalFunction` template argument to Finalizer<FinalFunction> allows a polymorphic function
+// type for storing the finalization function, such as `std::function` or `ftl::Function`.
+//
+// For convenience, this header defines a few useful aliases for using those types.
+//
+//   - `FinalizerStd`, an alias for `Finalizer<std::function<void()>>`
+//   - `FinalizerFtl`, an alias for `Finalizer<ftl::Function<void()>>`
+//   - `FinalizerFtl1`, an alias for `Finalizer<ftl::Function<void(), 1>>`
+//   - `FinalizerFtl2`, an alias for `Finalizer<ftl::Function<void(), 2>>`
+//   - `FinalizerFtl3`, an alias for `Finalizer<ftl::Function<void(), 3>>`
+//
+// Clients of this header are free to define other aliases they need.
+//
+// A Finalizer that uses a polymorphic function type can be returned from a function call and/or
+// stored as member data (to be destroyed along with the containing class).
+//
+//   auto register(Observer* observer) -> ftl::FinalizerStd<void()> {
+//      const auto id = observers.add(observer);
+//      return ftl::Finalizer([id]() { observers.remove(id); });
+//   }
+//
+//   {
+//     const auto _ = register(observer);
+//     // do the things that required the registered observer.
+//   }
+//   // the observer is removed.
+//
+// Cautions:
+//
+//   1. When a Finalizer is stored as member data, you will almost certainly want that cleanup to
+//      happen first, before the rest of the other member data is destroyed. For safety you should
+//      assume that the finalization function will access that data directly or indirectly.
+//
+//      This means that Finalizers should be defined last, after all other normal member data in a
+//      class.
+//
+//          class MyClass {
+//           public:
+//            bool initialize() {
+//              ready_ = true;
+//              cleanup_ = ftl::Finalizer([this]() { ready_ = false; });
+//              return true;
+//            }
+//
+//            bool ready_ = false;
+//
+//            // Finalizers should be last so other class members can be accessed before being
+//            // destroyed.
+//            ftl::FinalizerStd<void()> cleanup_;
+//          };
+//
+//   2. Care must be taken to use `ftl::Finalizer()` when constructing locally from a lambda. If you
+//      forget to do so, you are just creating a lambda that won't be automatically invoked!
+//
+//          const auto bad = [&counter](){ ++counter; }; // Just a lambda instance
+//          const auto good = ftl::Finalizer([&counter](){ ++counter; });
+//
+template <typename FinalFunction>
+class Finalizer final {
+  // requires(std::is_invocable_r_v<void, FinalFunction>)
+  static_assert(std::is_invocable_r_v<void, FinalFunction>);
+
+ public:
+  // A default constructed Finalizer does nothing when destroyed.
+  // requires(std::is_default_constructible_v<FinalFunction>)
+  constexpr Finalizer() = default;
+
+  // Constructs a Finalizer from a function object.
+  // requires(std::is_invocable_v<F>)
+  template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
+  [[nodiscard]] explicit constexpr Finalizer(F&& function)
+      : Finalizer(std::forward<F>(function), false) {}
+
+  constexpr ~Finalizer() { maybe_invoke(); }
+
+  // Disallow copying.
+  Finalizer(const Finalizer& that) = delete;
+  auto operator=(const Finalizer& that) = delete;
+
+  // Move construction
+  // requires(std::is_move_constructible_v<FinalFunction>)
+  [[nodiscard]] constexpr Finalizer(Finalizer&& that)
+      : Finalizer(std::move(that.function_), std::exchange(that.canceled_, true)) {}
+
+  // Implicit conversion move construction
+  // requires(!std::is_same_v<Finalizer, Finalizer<F>>)
+  template <typename F, typename = std::enable_if_t<!std::is_same_v<Finalizer, Finalizer<F>>>>
+  // NOLINTNEXTLINE(google-explicit-constructor, cppcoreguidelines-rvalue-reference-param-not-moved)
+  [[nodiscard]] constexpr Finalizer(Finalizer<F>&& that)
+      : Finalizer(std::move(that.function_), std::exchange(that.canceled_, true)) {}
+
+  // Move assignment
+  // requires(std::is_move_assignable_v<FinalFunction>)
+  constexpr auto operator=(Finalizer&& that) -> Finalizer& {
+    maybe_invoke();
+
+    function_ = std::move(that.function_);
+    canceled_ = std::exchange(that.canceled_, true);
+
+    return *this;
+  }
+
+  // Implicit conversion move assignment
+  // requires(!std::is_same_v<Finalizer, Finalizer<F>>)
+  template <typename F, typename = std::enable_if_t<!std::is_same_v<Finalizer, Finalizer<F>>>>
+  // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
+  constexpr auto operator=(Finalizer<F>&& that) -> Finalizer& {
+    *this = Finalizer(std::move(that.function_), std::exchange(that.canceled_, true));
+    return *this;
+  }
+
+  // Cancels the final function, preventing it from being invoked.
+  constexpr void cancel() {
+    canceled_ = true;
+    maybe_nullify_function();
+  }
+
+  // Invokes the final function now, if not already invoked.
+  constexpr void operator()() { maybe_invoke(); }
+
+ private:
+  template <typename>
+  friend class Finalizer;
+
+  template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
+  [[nodiscard]] explicit constexpr Finalizer(F&& function, bool canceled)
+      : function_(std::forward<F>(function)), canceled_(canceled) {}
+
+  constexpr void maybe_invoke() {
+    if (!std::exchange(canceled_, true)) {
+      std::invoke(function_);
+      maybe_nullify_function();
+    }
+  }
+
+  constexpr void maybe_nullify_function() {
+    // Sets function_ to nullptr if that is supported for the backing type.
+    if constexpr (std::is_assignable_v<FinalFunction, nullptr_t>) {
+      function_ = nullptr;
+    }
+  }
+
+  FinalFunction function_;
+  bool canceled_ = true;
+};
+
+template <typename F>
+Finalizer(F&&) -> Finalizer<std::decay_t<F>>;
+
+// A standard alias for using `std::function` as the polymorphic function type.
+using FinalizerStd = Finalizer<std::function<void()>>;
+
+// Helpful aliases for using `ftl::Function` as the polymorphic function type.
+using FinalizerFtl = Finalizer<Function<void()>>;
+using FinalizerFtl1 = Finalizer<Function<void(), 1>>;
+using FinalizerFtl2 = Finalizer<Function<void(), 2>>;
+using FinalizerFtl3 = Finalizer<Function<void(), 3>>;
+
+}  // namespace android::ftl
\ No newline at end of file
diff --git a/include/input/AccelerationCurve.h b/include/input/AccelerationCurve.h
index 0cf648a..8a4a5d4 100644
--- a/include/input/AccelerationCurve.h
+++ b/include/input/AccelerationCurve.h
@@ -46,4 +46,15 @@
 std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
         int32_t sensitivity);
 
+/*
+ * Creates a flat acceleration curve for disabling pointer acceleration.
+ *
+ * This method generates a single AccelerationCurveSegment with specific values
+ * to effectively disable acceleration for both mice and touchpads.
+ * A flat acceleration curve ensures a constant gain, meaning that the output
+ * velocity is directly proportional to the input velocity, resulting in
+ * a 1:1 movement ratio between the input device and the on-screen pointer.
+ */
+std::vector<AccelerationCurveSegment> createFlatAccelerationCurve(int32_t sensitivity);
+
 } // namespace android
diff --git a/include/private/OWNERS b/include/private/OWNERS
index 37da96d..db3ae48 100644
--- a/include/private/OWNERS
+++ b/include/private/OWNERS
@@ -1,3 +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 e3f98ba..a468313 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -125,8 +125,10 @@
 /**
  * Creates a session using ASessionCreationConfig
  */
-APerformanceHintSession* APerformanceHint_createSessionUsingConfigInternal(
-        APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig,
+int APerformanceHint_createSessionUsingConfigInternal(
+        APerformanceHintManager* manager,
+        ASessionCreationConfig* config,
+        APerformanceHintSession** sessionOut,
         SessionTag tag);
 
 /**
diff --git a/include/private/system_health_private.h b/include/private/system_health_private.h
new file mode 100644
index 0000000..05a5a06
--- /dev/null
+++ b/include/private/system_health_private.h
@@ -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.
+ */
+
+#ifndef ANDROID_PRIVATE_NATIVE_SYSTEM_HEALTH_H
+#define ANDROID_PRIVATE_NATIVE_SYSTEM_HEALTH_H
+
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/**
+ * For testing only.
+ */
+void ASystemHealth_setIHintManagerForTesting(void* iManager);
+
+__END_DECLS
+
+#endif // ANDROID_PRIVATE_NATIVE_SYSTEM_HEALTH_H
+
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index aac369d..fb00d4f 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -269,6 +269,7 @@
         "-Wzero-as-null-pointer-constant",
         "-Wreorder-init-list",
         "-Wunused-const-variable",
+        "-Wunused-result",
         "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
         // Hide symbols by default and set the BUILDING_LIBBINDER macro so that
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index 34d5a09..7c0319a 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -15,6 +15,7 @@
  */
 #include "BackendUnifiedServiceManager.h"
 
+#include <android-base/strings.h>
 #include <android/os/IAccessor.h>
 #include <android/os/IServiceManager.h>
 #include <binder/RpcSession.h>
@@ -47,6 +48,9 @@
 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",
@@ -211,7 +215,9 @@
                                                 sp<IBinder>* _aidl_return) {
     os::Service service;
     Status status = getService2(name, &service);
-    *_aidl_return = service.get<os::Service::Tag::serviceWithMetadata>().service;
+    if (status.isOk()) {
+        *_aidl_return = service.get<os::Service::Tag::serviceWithMetadata>().service;
+    }
     return status;
 }
 
@@ -220,7 +226,10 @@
         return Status::ok();
     }
     os::Service service;
-    Status status = mTheRealServiceManager->getService2(name, &service);
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->getService2(name, &service);
+    }
 
     if (status.isOk()) {
         status = toBinderService(name, service, _out);
@@ -231,13 +240,26 @@
     return status;
 }
 
-Status BackendUnifiedServiceManager::checkService(const ::std::string& name, os::Service* _out) {
+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;
+}
+
+Status BackendUnifiedServiceManager::checkService2(const ::std::string& name, os::Service* _out) {
     os::Service service;
     if (returnIfCached(name, _out)) {
         return Status::ok();
     }
 
-    Status status = mTheRealServiceManager->checkService(name, &service);
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->checkService2(name, &service);
+    }
     if (status.isOk()) {
         status = toBinderService(name, service, _out);
         if (status.isOk()) {
@@ -315,66 +337,156 @@
 Status BackendUnifiedServiceManager::addService(const ::std::string& name,
                                                 const sp<IBinder>& service, bool allowIsolated,
                                                 int32_t dumpPriority) {
-    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);
+    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;
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
 Status BackendUnifiedServiceManager::listServices(int32_t dumpPriority,
                                                   ::std::vector<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->listServices(dumpPriority, _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);
 }
 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);
 }
 Status BackendUnifiedServiceManager::isDeclared(const ::std::string& name, bool* _aidl_return) {
-    return mTheRealServiceManager->isDeclared(name, _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;
 }
 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;
 }
 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);
 }
 Status BackendUnifiedServiceManager::getUpdatableNames(const ::std::string& apexName,
                                                        ::std::vector<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->getUpdatableNames(apexName, _aidl_return);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->getUpdatableNames(apexName, _aidl_return);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
 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);
 }
 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);
 }
 Status BackendUnifiedServiceManager::tryUnregisterService(const ::std::string& name,
                                                           const sp<IBinder>& service) {
-    return mTheRealServiceManager->tryUnregisterService(name, service);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->tryUnregisterService(name, service);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
 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)) {
@@ -384,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) {
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index 6a0d06a..c14f280 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -122,7 +122,8 @@
 
     binder::Status getService(const ::std::string& name, sp<IBinder>* _aidl_return) override;
     binder::Status getService2(const ::std::string& name, os::Service* out) override;
-    binder::Status checkService(const ::std::string& name, os::Service* out) override;
+    binder::Status checkService(const ::std::string& name, sp<IBinder>* _aidl_return) override;
+    binder::Status checkService2(const ::std::string& name, os::Service* out) override;
     binder::Status addService(const ::std::string& name, const sp<IBinder>& service,
                               bool allowIsolated, int32_t dumpPriority) override;
     binder::Status listServices(int32_t dumpPriority,
@@ -167,5 +168,9 @@
 sp<BackendUnifiedServiceManager> getBackendUnifiedServiceManager();
 
 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 53bd08d..0a22588 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -288,7 +288,7 @@
     // for below objects
     RpcMutex mLock;
     std::set<sp<RpcServerLink>> mRpcServerLinks;
-    BpBinder::ObjectManager mObjects;
+    BpBinder::ObjectManager mObjectMgr;
 
     unique_fd mRecordingFd;
 };
@@ -468,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
@@ -477,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) {
@@ -485,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) {
@@ -501,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 3758b65..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,20 +153,6 @@
     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, std::function<void()>* postTask) {
@@ -697,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) {
@@ -720,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()
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 623e7b9..f191b97 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -626,12 +626,22 @@
 {
     if (mProcess->mDriverFD < 0)
         return;
-    talkWithDriver(false);
+
+    if (status_t res = talkWithDriver(false); res != OK) {
+        // TODO: we may want to abort for some of these cases
+        ALOGW("1st call to talkWithDriver returned error in flushCommands: %s",
+              statusToString(res).c_str());
+    }
+
     // The flush could have caused post-write refcount decrements to have
     // been executed, which in turn could result in BC_RELEASE/BC_DECREFS
     // being queued in mOut. So flush again, if we need to.
     if (mOut.dataSize() > 0) {
-        talkWithDriver(false);
+        if (status_t res = talkWithDriver(false); res != OK) {
+            // TODO: we may want to abort for some of these cases
+            ALOGW("2nd call to talkWithDriver returned error in flushCommands: %s",
+                  statusToString(res).c_str());
+        }
     }
     if (mOut.dataSize() > 0) {
         ALOGW("mOut.dataSize() > 0 after flushCommands()");
@@ -803,7 +813,11 @@
 
     mOut.writeInt32(BC_EXIT_LOOPER);
     mIsLooper = false;
-    talkWithDriver(false);
+    if (status_t res = talkWithDriver(false); res != OK) {
+        // TODO: we may want to abort for some of these cases
+        ALOGW("call to talkWithDriver in joinThreadPool returned error: %s, FD: %d",
+              statusToString(res).c_str(), mProcess->mDriverFD);
+    }
     size_t oldCount = mProcess->mCurrentThreads.fetch_sub(1);
     LOG_ALWAYS_FATAL_IF(oldCount == 0,
                         "Threadpool thread count underflowed. Thread cannot exist and exit in "
@@ -839,12 +853,8 @@
 
 void IPCThreadState::stopProcess(bool /*immediate*/)
 {
-    //ALOGI("**** STOPPING PROCESS");
-    flushCommands();
-    int fd = mProcess->mDriverFD;
-    mProcess->mDriverFD = -1;
-    close(fd);
-    //kill(getpid(), SIGKILL);
+    ALOGI("IPCThreadState::stopProcess() (deprecated) called. Exiting process.");
+    exit(0);
 }
 
 status_t IPCThreadState::transact(int32_t handle,
@@ -1494,7 +1504,14 @@
                 buffer.setDataSize(0);
 
                 constexpr uint32_t kForwardReplyFlags = TF_CLEAR_BUF;
-                sendReply(reply, (tr.flags & kForwardReplyFlags));
+
+                // TODO: we may want to abort if there is an error here, or return as 'error'
+                // from this function, but the impact needs to be measured
+                status_t error2 = sendReply(reply, (tr.flags & kForwardReplyFlags));
+                if (error2 != OK) {
+                    ALOGE("error in sendReply for synchronous call: %s",
+                          statusToString(error2).c_str());
+                }
             } else {
                 if (error != OK) {
                     std::ostringstream logStream;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 53435c3..719e445 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -304,6 +304,25 @@
     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, []() {
@@ -605,7 +624,7 @@
 
 sp<IBinder> CppBackendShim::checkService(const String16& name) const {
     Service ret;
-    if (!mUnifiedServiceManager->checkService(String8(name).c_str(), &ret).isOk()) {
+    if (!mUnifiedServiceManager->checkService2(String8(name).c_str(), &ret).isOk()) {
         return nullptr;
     }
     return ret.get<Service::Tag::serviceWithMetadata>().service;
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 0b7cd81..c0ebee0 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -1293,10 +1293,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); }
@@ -1352,10 +1348,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);
 }
@@ -3397,14 +3389,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();
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 9e5e79f..4332f8a 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -37,6 +37,9 @@
       "name": "binderStabilityTest"
     },
     {
+      "name": "binderStabilityIntegrationTest"
+    },
+    {
       "name": "binderRpcWireProtocolTest"
     },
     {
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index 69edef8..6539238 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -83,11 +83,20 @@
 
     /**
      * Retrieve an existing service called @a name from the service
+     * manager. Non-blocking. Returns null if the service does not exist.
+     *
+     * @deprecated TODO(b/355394904): Use checkService2 instead. This does not
+     * return metadata that is included in ServiceWithMetadata
+     */
+    @UnsupportedAppUsage
+    @nullable IBinder checkService(@utf8InCpp String name);
+
+    /**
+     * Retrieve an existing service called @a name from the service
      * manager. Non-blocking. Returns null if the service does not
      * exist.
      */
-    @UnsupportedAppUsage
-    Service checkService(@utf8InCpp String name);
+    Service checkService2(@utf8InCpp String name);
 
     /**
      * Place a new @a service called @a name into the service
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 7518044..935bd8d 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -104,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();
@@ -116,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&);
@@ -224,7 +223,7 @@
     volatile int32_t mObitsSent;
     Vector<Obituary>* mObituaries;
     std::unique_ptr<FrozenStateChange> mFrozen;
-    ObjectManager mObjects;
+    ObjectManager mObjectMgr;
     mutable String16 mDescriptorCache;
     int32_t mTrackedUid;
 
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index cdee17c..bb45ad2 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -243,7 +243,6 @@
         "android.media.IMediaHTTPService",
         "android.media.IMediaLogService",
         "android.media.IMediaMetadataRetriever",
-        "android.media.IMediaMetricsService",
         "android.media.IMediaPlayer",
         "android.media.IMediaPlayerClient",
         "android.media.IMediaPlayerService",
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 9ef4e69..f7465e2 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -64,7 +64,10 @@
      * Returns the PID of the process which has made the current binder
      * call. If not in a binder call, this will return getpid.
      *
-     * Warning: oneway transactions do not receive PID. Even if you expect
+     * Warning do not use this as a security identifier! PID is unreliable
+     * as it may be re-used. This should mostly be used for debugging.
+     *
+     * oneway transactions do not receive PID. Even if you expect
      * a transaction to be synchronous, a misbehaving client could send it
      * as an asynchronous call and result in a 0 PID here. Additionally, if
      * there is a race and the calling process dies, the PID may still be
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 7d79baa..d248f22 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -80,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,
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index b4efa0a..6c4c6fe 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -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.
@@ -627,11 +627,13 @@
     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;
@@ -685,6 +687,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;
@@ -1485,14 +1488,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/SafeInterface.h b/libs/binder/include/binder/SafeInterface.h
index 0b4f196..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:
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/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index bd46c47..d69d318 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -419,6 +419,9 @@
  * This can be used with higher-level system services to determine the caller's identity and check
  * permissions.
  *
+ * Warning do not use this as a security identifier! PID is unreliable as it may be re-used. This
+ * should mostly be used for debugging.
+ *
  * Available since API level 29.
  *
  * \return calling uid or the current process's UID if this thread isn't processing a transaction.
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/rust/src/state.rs b/libs/binder/rust/src/state.rs
index 8a06274..c0cac83 100644
--- a/libs/binder/rust/src/state.rs
+++ b/libs/binder/rust/src/state.rs
@@ -101,7 +101,10 @@
     /// dies and is replaced with another process with elevated permissions and
     /// the same PID.
     ///
-    /// Warning: oneway transactions do not receive PID. Even if you expect
+    /// Warning: do not use this as a security identifier! PID is unreliable
+    /// as it may be re-used. This should mostly be used for debugging.
+    ///
+    /// oneway transactions do not receive PID. Even if you expect
     /// a transaction to be synchronous, a misbehaving client could send it
     /// as a synchronous call and result in a 0 PID here. Additionally, if
     /// there is a race and the calling process dies, the PID may still be
diff --git a/libs/binder/rust/src/system_only.rs b/libs/binder/rust/src/system_only.rs
index 1a58d6b..50aa336 100644
--- a/libs/binder/rust/src/system_only.rs
+++ b/libs/binder/rust/src/system_only.rs
@@ -23,22 +23,17 @@
 use std::os::raw::c_char;
 
 use libc::{sockaddr, sockaddr_un, sockaddr_vm, socklen_t};
-use std::sync::Arc;
-use std::{fmt, mem, ptr};
+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,
 }
 
-impl fmt::Debug for Accessor {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "ABinderRpc_Accessor({:p})", self.accessor)
-    }
-}
-
 /// Socket connection info required for libbinder to connect to a service.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum ConnectionInfo {
@@ -70,7 +65,7 @@
     where
         F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
     {
-        let callback: *mut c_void = Arc::into_raw(Arc::new(callback)) as *mut c_void;
+        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.
@@ -154,7 +149,7 @@
     ///   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 an `Arc<F>` and
+    /// - 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,
@@ -167,7 +162,7 @@
             log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
             return ptr::null_mut();
         }
-        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        // 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
@@ -212,19 +207,19 @@
         }
     }
 
-    /// Callback that decrements the ref-count.
+    /// 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 an `Arc<F>` and
+    /// - 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 an Arc<F>.
-        unsafe { Arc::decrement_strong_count(cookie as *const F) };
+        // Safety: The caller promises that `cookie` is for a Box<F>.
+        unsafe { std::mem::drop(Box::from_raw(cookie as *mut F)) };
     }
 }
 
@@ -301,7 +296,7 @@
     where
         F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
     {
-        let callback: *mut c_void = Arc::into_raw(Arc::new(provider)) as *mut c_void;
+        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> =
@@ -351,7 +346,7 @@
             log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
             return ptr::null_mut();
         }
-        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        // Safety: The caller promises that `cookie` is for a Box<F>.
         let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
 
         let inst = {
@@ -382,14 +377,14 @@
     ///
     /// # Safety
     ///
-    /// - The `cookie` parameter must be the cookie for an `Arc<F>` and
+    /// - 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 an Arc<F>.
-        unsafe { Arc::decrement_strong_count(cookie as *const F) };
+        // Safety: The caller promises that `cookie` is for a Box<F>.
+        unsafe { std::mem::drop(Box::from_raw(cookie as *mut F)) };
     }
 }
 
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index be99065..78fe2a8 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -127,7 +127,12 @@
         // We can't send BpBinder for regular binder over RPC.
         return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
     }
-    android::binder::Status checkService(const std::string&, android::os::Service*) override {
+    android::binder::Status checkService(const std::string&,
+                                         android::sp<android::IBinder>*) override {
+        // We can't send BpBinder for regular binder over RPC.
+        return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
+    }
+    android::binder::Status checkService2(const std::string&, android::os::Service*) override {
         // We can't send BpBinder for regular binder over RPC.
         return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
     }
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index c21d7c6..f412dfb 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -803,6 +803,28 @@
 }
 
 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,
+}
+
+cc_test {
     name: "binderAllocationLimits",
     defaults: ["binder_test_defaults"],
     srcs: ["binderAllocationLimits.cpp"],
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
index 19395c2..121e5ae 100644
--- a/libs/binder/tests/binderCacheUnitTest.cpp
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -74,7 +74,7 @@
 public:
     MockAidlServiceManager() : innerSm() {}
 
-    binder::Status checkService(const ::std::string& name, os::Service* _out) override {
+    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;
@@ -98,7 +98,7 @@
 public:
     MockAidlServiceManager2() : innerSm() {}
 
-    binder::Status checkService(const ::std::string& name, os::Service* _out) override {
+    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;
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index c038c95..891c0a2 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -44,6 +44,7 @@
 #include <processgroup/processgroup.h>
 #include <utils/Flattenable.h>
 #include <utils/SystemClock.h>
+#include "binder/IServiceManagerUnitTestHelper.h"
 
 #include <linux/sched.h>
 #include <sys/epoll.h>
@@ -585,14 +586,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));
@@ -600,12 +601,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));
 
@@ -1788,6 +1784,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();
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index da5a8e3..9f656ec 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1328,6 +1328,109 @@
     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,
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index c6fd487..4b9dcf8 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -498,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 849dc7c..45b2103 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -789,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;
         }
 
@@ -858,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) {
@@ -875,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 a62ad96..ca70b66 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -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/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 07f0143..b2ba1ae 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -121,6 +121,11 @@
     PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors),
     PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders),
     PARCEL_READ_NO_STATUS(std::vector<int>, debugReadAllFileDescriptors),
+    [] (const ::android::Parcel& p, FuzzedDataProvider&) {
+        FUZZ_LOG() << "about to markSensitive";
+        p.markSensitive();
+        FUZZ_LOG() << "markSensitive done";
+    },
     [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
         std::string interface = provider.ConsumeRandomLengthString();
         FUZZ_LOG() << "about to enforceInterface: " << interface;
@@ -312,8 +317,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/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index dfd178a..61b9612 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -40,6 +40,13 @@
     const uint8_t fuzzerParcelOptions = provider.ConsumeIntegral<uint8_t>();
     const bool resultShouldBeView = fuzzerParcelOptions & 1;
     const bool resultShouldBeRpc = fuzzerParcelOptions & 2;
+    const bool resultShouldMarkSensitive = fuzzerParcelOptions & 4;
+
+    auto sensitivity_guard = binder::impl::make_scope_guard([&]() {
+        if (resultShouldMarkSensitive) {
+            outputParcel->markSensitive();
+        }
+    });
 
     Parcel* p;
     if (resultShouldBeView) {
@@ -49,6 +56,9 @@
     } else {
         p = outputParcel; // directly fill out the output Parcel
     }
+
+    // must be last guard, so outputParcel gets setup as view before
+    // other guards
     auto viewify_guard = binder::impl::make_scope_guard([&]() {
         if (resultShouldBeView) {
             outputParcel->makeDangerousViewOf(p);
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/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
index 6bf79d4..a8acded 100644
--- a/libs/debugstore/rust/src/core.rs
+++ b/libs/debugstore/rust/src/core.rs
@@ -48,7 +48,7 @@
     ///
     /// This constant is used as a part of the debug store's data format,
     /// allowing for version tracking and compatibility checks.
-    const ENCODE_VERSION: u32 = 1;
+    const ENCODE_VERSION: u32 = 2;
 
     /// Creates a new instance of `DebugStore` with specified event limit and maximum delay.
     fn new() -> Self {
@@ -129,7 +129,7 @@
         write!(
             f,
             "{}",
-            self.event_store.fold(String::new(), |mut acc, event| {
+            self.event_store.rfold(String::new(), |mut acc, event| {
                 if !acc.is_empty() {
                     acc.push_str("||");
                 }
diff --git a/libs/debugstore/rust/src/storage.rs b/libs/debugstore/rust/src/storage.rs
index 2ad7f4e..47760f3 100644
--- a/libs/debugstore/rust/src/storage.rs
+++ b/libs/debugstore/rust/src/storage.rs
@@ -32,14 +32,18 @@
         self.insertion_buffer.force_push(value);
     }
 
-    /// Folds over the elements in the storage using the provided function.
-    pub fn fold<U, F>(&self, init: U, mut func: F) -> U
+    /// Folds over the elements in the storage in reverse order using the provided function.
+    pub fn rfold<U, F>(&self, init: U, mut func: F) -> U
     where
         F: FnMut(U, &T) -> U,
     {
-        let mut acc = init;
+        let mut items = Vec::new();
         while let Some(value) = self.insertion_buffer.pop() {
-            acc = func(acc, &value);
+            items.push(value);
+        }
+        let mut acc = init;
+        for value in items.iter().rev() {
+            acc = func(acc, value);
         }
         acc
     }
@@ -59,18 +63,18 @@
         let storage = Storage::<i32, 10>::new();
         storage.insert(7);
 
-        let sum = storage.fold(0, |acc, &x| acc + x);
+        let sum = storage.rfold(0, |acc, &x| acc + x);
         assert_eq!(sum, 7, "The sum of the elements should be equal to the inserted value.");
     }
 
     #[test]
-    fn test_fold_functionality() {
+    fn test_rfold_functionality() {
         let storage = Storage::<i32, 5>::new();
         storage.insert(1);
         storage.insert(2);
         storage.insert(3);
 
-        let sum = storage.fold(0, |acc, &x| acc + x);
+        let sum = storage.rfold(0, |acc, &x| acc + x);
         assert_eq!(
             sum, 6,
             "The sum of the elements should be equal to the sum of inserted values."
@@ -84,13 +88,13 @@
         storage.insert(2);
         storage.insert(5);
 
-        let first_sum = storage.fold(0, |acc, &x| acc + x);
+        let first_sum = storage.rfold(0, |acc, &x| acc + x);
         assert_eq!(first_sum, 8, "The sum of the elements should be equal to the inserted values.");
 
         storage.insert(30);
         storage.insert(22);
 
-        let second_sum = storage.fold(0, |acc, &x| acc + x);
+        let second_sum = storage.rfold(0, |acc, &x| acc + x);
         assert_eq!(
             second_sum, 52,
             "The sum of the elements should be equal to the inserted values."
@@ -103,7 +107,7 @@
         storage.insert(1);
         // This value should overwrite the previously inserted value (1).
         storage.insert(4);
-        let sum = storage.fold(0, |acc, &x| acc + x);
+        let sum = storage.rfold(0, |acc, &x| acc + x);
         assert_eq!(sum, 4, "The sum of the elements should be equal to the inserted values.");
     }
 
@@ -128,7 +132,24 @@
             thread.join().expect("Thread should finish without panicking");
         }
 
-        let count = storage.fold(0, |acc, _| acc + 1);
+        let count = storage.rfold(0, |acc, _| acc + 1);
         assert_eq!(count, 100, "Storage should be filled to its limit with concurrent insertions.");
     }
+
+    #[test]
+    fn test_rfold_order() {
+        let storage = Storage::<i32, 5>::new();
+        storage.insert(1);
+        storage.insert(2);
+        storage.insert(3);
+
+        let mut result = Vec::new();
+        storage.rfold((), |_, &x| result.push(x));
+
+        assert_eq!(
+            result,
+            vec![3, 2, 1],
+            "Elements should be processed in reverse order of insertion"
+        );
+    }
 }
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 368f5e0..08ce855 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -21,6 +21,7 @@
         "enum_test.cpp",
         "expected_test.cpp",
         "fake_guard_test.cpp",
+        "finalizer_test.cpp",
         "flags_test.cpp",
         "function_test.cpp",
         "future_test.cpp",
diff --git a/libs/ftl/finalizer_test.cpp b/libs/ftl/finalizer_test.cpp
new file mode 100644
index 0000000..4f5c225
--- /dev/null
+++ b/libs/ftl/finalizer_test.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 <memory>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/finalizer.h>
+#include <gtest/gtest.h>
+
+namespace android::test {
+
+namespace {
+
+struct Counter {
+  constexpr auto increment_fn() {
+    return [this] { ++value_; };
+  }
+
+  auto increment_finalizer() {
+    return ftl::Finalizer([this] { ++value_; });
+  }
+
+  [[nodiscard]] constexpr auto value() const -> int { return value_; }
+
+ private:
+  int value_ = 0;
+};
+
+struct CounterPair {
+  constexpr auto increment_first_fn() { return first.increment_fn(); }
+  constexpr auto increment_second_fn() { return second.increment_fn(); }
+  [[nodiscard]] constexpr auto values() const -> std::pair<int, int> {
+    return {first.value(), second.value()};
+  }
+
+ private:
+  Counter first;
+  Counter second;
+};
+
+}  // namespace
+
+TEST(Finalizer, DefaultConstructionAndNoOpDestructionWhenPolymorphicType) {
+  ftl::FinalizerStd finalizer1;
+  ftl::FinalizerFtl finalizer2;
+  ftl::FinalizerFtl1 finalizer3;
+  ftl::FinalizerFtl2 finalizer4;
+  ftl::FinalizerFtl3 finalizer5;
+}
+
+TEST(Finalizer, InvokesTheFunctionOnDestruction) {
+  Counter counter;
+  {
+    const auto finalizer = counter.increment_finalizer();
+    EXPECT_EQ(counter.value(), 0);
+  }
+  EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, InvocationCanBeCanceled) {
+  Counter counter;
+  {
+    auto finalizer = counter.increment_finalizer();
+    EXPECT_EQ(counter.value(), 0);
+    finalizer.cancel();
+    EXPECT_EQ(counter.value(), 0);
+  }
+  EXPECT_EQ(counter.value(), 0);
+}
+
+TEST(Finalizer, InvokesTheFunctionOnce) {
+  Counter counter;
+  {
+    auto finalizer = counter.increment_finalizer();
+    EXPECT_EQ(counter.value(), 0);
+    finalizer();
+    EXPECT_EQ(counter.value(), 1);
+    finalizer();
+    EXPECT_EQ(counter.value(), 1);
+  }
+  EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, SelfInvocationIsAllowedAndANoOp) {
+  Counter counter;
+  ftl::FinalizerStd finalizer;
+  finalizer = ftl::Finalizer([&]() {
+    counter.increment_fn()();
+    finalizer();  // recursive invocation should do nothing.
+  });
+  EXPECT_EQ(counter.value(), 0);
+  finalizer();
+  EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, MoveConstruction) {
+  Counter counter;
+  {
+    ftl::FinalizerStd outer_finalizer = counter.increment_finalizer();
+    EXPECT_EQ(counter.value(), 0);
+    {
+      ftl::FinalizerStd inner_finalizer = std::move(outer_finalizer);
+      static_assert(std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+      EXPECT_EQ(counter.value(), 0);
+    }
+    EXPECT_EQ(counter.value(), 1);
+  }
+  EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, MoveConstructionWithImplicitConversion) {
+  Counter counter;
+  {
+    auto outer_finalizer = counter.increment_finalizer();
+    EXPECT_EQ(counter.value(), 0);
+    {
+      ftl::FinalizerStd inner_finalizer = std::move(outer_finalizer);
+      static_assert(!std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+      EXPECT_EQ(counter.value(), 0);
+    }
+    EXPECT_EQ(counter.value(), 1);
+  }
+  EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, MoveAssignment) {
+  CounterPair pair;
+  {
+    ftl::FinalizerStd outer_finalizer = ftl::Finalizer(pair.increment_first_fn());
+    EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+
+    {
+      ftl::FinalizerStd inner_finalizer = ftl::Finalizer(pair.increment_second_fn());
+      static_assert(std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+      EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+      inner_finalizer = std::move(outer_finalizer);
+      EXPECT_EQ(pair.values(), std::make_pair(0, 1));
+    }
+    EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+  }
+  EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+}
+
+TEST(Finalizer, MoveAssignmentWithImplicitConversion) {
+  CounterPair pair;
+  {
+    auto outer_finalizer = ftl::Finalizer(pair.increment_first_fn());
+    EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+
+    {
+      ftl::FinalizerStd inner_finalizer = ftl::Finalizer(pair.increment_second_fn());
+      static_assert(!std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+      EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+      inner_finalizer = std::move(outer_finalizer);
+      EXPECT_EQ(pair.values(), std::make_pair(0, 1));
+    }
+    EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+  }
+  EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+}
+
+TEST(Finalizer, NullifiesTheFunctionWhenInvokedIfPossible) {
+  auto shared = std::make_shared<int>(0);
+  std::weak_ptr<int> weak = shared;
+
+  int count = 0;
+  {
+    auto lambda = [capture = std::move(shared)]() {};
+    auto finalizer = ftl::Finalizer(std::move(lambda));
+    EXPECT_FALSE(weak.expired());
+
+    // A lambda is not nullable. Invoking the finalizer cannot destroy it to destroy the lambda's
+    // capture.
+    finalizer();
+    EXPECT_FALSE(weak.expired());
+  }
+  // The lambda is only destroyed when the finalizer instance is destroyed.
+  EXPECT_TRUE(weak.expired());
+
+  shared = std::make_shared<int>(0);
+  weak = shared;
+
+  {
+    auto lambda = [capture = std::move(shared)]() {};
+    auto finalizer = ftl::FinalizerStd(std::move(lambda));
+    EXPECT_FALSE(weak.expired());
+
+    // Since std::function is used, and is nullable, invoking the finalizer will destroy the
+    // contained function, which will destroy the lambda's capture.
+    finalizer();
+    EXPECT_TRUE(weak.expired());
+  }
+}
+
+}  // namespace android::test
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index af50a29..1fde45b 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -21,9 +21,25 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "graphicsenv_flags",
+    package: "com.android.graphics.graphicsenv.flags",
+    container: "system",
+    srcs: ["graphicsenv_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "graphicsenv_flags_c_lib",
+    aconfig_declarations: "graphicsenv_flags",
+}
+
 cc_library_shared {
     name: "libgraphicsenv",
 
+    defaults: [
+        "aconfig_lib_cc_static_link.defaults",
+    ],
+
     srcs: [
         "GpuStatsInfo.cpp",
         "GraphicsEnv.cpp",
@@ -35,6 +51,10 @@
         "-Werror",
     ],
 
+    static_libs: [
+        "graphicsenv_flags_c_lib",
+    ],
+
     shared_libs: [
         "libbase",
         "libbinder",
@@ -42,6 +62,7 @@
         "libdl_android",
         "liblog",
         "libutils",
+        "server_configurable_flags",
     ],
 
     header_libs: [
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index a8d5fe7..4bc2611 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -29,6 +29,7 @@
 #include <android-base/strings.h>
 #include <android/dlext.h>
 #include <binder/IServiceManager.h>
+#include <com_android_graphics_graphicsenv_flags.h>
 #include <graphicsenv/IGpuService.h>
 #include <log/log.h>
 #include <nativeloader/dlext_namespaces.h>
@@ -70,6 +71,8 @@
 }
 } // namespace
 
+namespace graphicsenv_flags = com::android::graphics::graphicsenv::flags;
+
 namespace android {
 
 enum NativeLibrary {
@@ -596,7 +599,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
@@ -606,11 +609,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;
     }
@@ -624,10 +627,36 @@
     return mPackageName;
 }
 
+// List of ANGLE features to enable, specified in the Global.Settings value "angle_egl_features".
 const std::vector<std::string>& GraphicsEnv::getAngleEglFeatures() {
     return mAngleEglFeatures;
 }
 
+void GraphicsEnv::getAngleFeatureOverrides(std::vector<const char*>& enabled,
+                                           std::vector<const char*>& disabled) {
+    if (!graphicsenv_flags::feature_overrides()) {
+        return;
+    }
+
+    for (const FeatureConfig& feature : mFeatureOverrides.mGlobalFeatures) {
+        if (feature.mEnabled) {
+            enabled.push_back(feature.mFeatureName.c_str());
+        } else {
+            disabled.push_back(feature.mFeatureName.c_str());
+        }
+    }
+
+    if (mFeatureOverrides.mPackageFeatures.count(mPackageName)) {
+        for (const FeatureConfig& feature : mFeatureOverrides.mPackageFeatures[mPackageName]) {
+            if (feature.mEnabled) {
+                enabled.push_back(feature.mFeatureName.c_str());
+            } else {
+                disabled.push_back(feature.mFeatureName.c_str());
+            }
+        }
+    }
+}
+
 android_namespace_t* GraphicsEnv::getAngleNamespace() {
     std::lock_guard<std::mutex> lock(mNamespaceMutex);
 
diff --git a/libs/graphicsenv/graphicsenv_flags.aconfig b/libs/graphicsenv/graphicsenv_flags.aconfig
new file mode 100644
index 0000000..ac66362
--- /dev/null
+++ b/libs/graphicsenv/graphicsenv_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.graphics.graphicsenv.flags"
+container: "system"
+
+flag {
+  name: "feature_overrides"
+  namespace: "core_graphics"
+  description: "This flag controls the Feature Overrides in GraphicsEnv."
+  bug: "372694741"
+}
diff --git a/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
new file mode 100644
index 0000000..2ef54ad
--- /dev/null
+++ b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace android {
+
+class FeatureConfig {
+public:
+    FeatureConfig() = default;
+    FeatureConfig(const FeatureConfig&) = default;
+    virtual ~FeatureConfig() = default;
+
+    std::string mFeatureName;
+    bool mEnabled;
+};
+
+/*
+ * Class for transporting OpenGL ES Feature configurations from GpuService to authorized
+ * recipients.
+ */
+class FeatureOverrides {
+public:
+    FeatureOverrides() = default;
+    FeatureOverrides(const FeatureOverrides&) = default;
+    virtual ~FeatureOverrides() = default;
+
+    std::vector<FeatureConfig> mGlobalFeatures;
+    /* Key: Package Name, Value: Package's Feature Configs */
+    std::map<std::string, std::vector<FeatureConfig>> mPackageFeatures;
+};
+
+} // namespace android
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index b0ab0b9..55fa13a 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_UI_GRAPHICS_ENV_H
 #define ANDROID_UI_GRAPHICS_ENV_H 1
 
+#include <graphicsenv/FeatureOverrides.h>
 #include <graphicsenv/GpuStatsInfo.h>
 
 #include <mutex>
@@ -114,12 +115,14 @@
     // 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.
     std::string& getPackageName();
     const std::vector<std::string>& getAngleEglFeatures();
+    void getAngleFeatureOverrides(std::vector<const char*>& enabled,
+                                  std::vector<const char*>& disabled);
     // Set the persist.graphics.egl system property value.
     void nativeToggleAngleAsSystemDriver(bool enabled);
     bool shouldUseSystemAngle();
@@ -177,6 +180,7 @@
     std::string mPackageName;
     // ANGLE EGL features;
     std::vector<std::string> mAngleEglFeatures;
+    FeatureOverrides mFeatureOverrides;
     // Whether ANGLE should be used.
     bool mShouldUseAngle = false;
     // Whether loader should load system ANGLE.
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index b0f6e69..f1374e2 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -108,6 +108,15 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void BufferQueue::ProxyConsumerListener::onSlotCountChanged(int slotCount) {
+    sp<ConsumerListener> listener(mConsumerListener.promote());
+    if (listener != nullptr) {
+        listener->onSlotCountChanged(slotCount);
+    }
+}
+#endif
+
 void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
         sp<IGraphicBufferConsumer>* outConsumer,
         bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 9855b5b..f012586 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -341,9 +341,9 @@
             return BAD_VALUE;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
-                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isAcquired()) {
             BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
@@ -483,10 +483,13 @@
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
 
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
-            releaseFence == nullptr) {
-        BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
-                releaseFence.get());
+    const int totalSlotCount = mCore->getTotalSlotCountLocked();
+    if (slot < 0 || slot >= totalSlotCount) {
+        BQ_LOGE("releaseBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
+        return BAD_VALUE;
+    }
+    if (releaseFence == nullptr) {
+        BQ_LOGE("releaseBuffer: slot %d fence %p NULL", slot, releaseFence.get());
         return BAD_VALUE;
     }
 
@@ -515,6 +518,13 @@
     { // Autolock scope
         std::lock_guard<std::mutex> lock(mCore->mMutex);
 
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount || releaseFence == nullptr) {
+            BQ_LOGE("releaseBuffer: slot %d out of range [0, %d) or fence %p NULL", slot,
+                    totalSlotCount, releaseFence.get());
+            return BAD_VALUE;
+        }
+
         // If the frame number has changed because the buffer has been reallocated,
         // we can ignore this releaseBuffer for the old buffer.
         // Ignore this for the shared buffer where the frame number can easily
@@ -661,6 +671,43 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueConsumer::getReleasedBuffersExtended(std::vector<bool>* outSlotMask) {
+    ATRACE_CALL();
+
+    if (outSlotMask == nullptr) {
+        BQ_LOGE("getReleasedBuffersExtended: outSlotMask may not be NULL");
+        return BAD_VALUE;
+    }
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mIsAbandoned) {
+        BQ_LOGE("getReleasedBuffersExtended: BufferQueue has been abandoned");
+        return NO_INIT;
+    }
+
+    const int totalSlotCount = mCore->getTotalSlotCountLocked();
+    outSlotMask->resize(totalSlotCount);
+    for (int s = 0; s < totalSlotCount; ++s) {
+        (*outSlotMask)[s] = !mSlots[s].mAcquireCalled;
+    }
+
+    // Remove from the mask queued buffers for which acquire has been called,
+    // since the consumer will not receive their buffer addresses and so must
+    // retain their cached information
+    BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+    while (current != mCore->mQueue.end()) {
+        if (current->mAcquireCalled) {
+            (*outSlotMask)[current->mSlot] = false;
+        }
+        ++current;
+    }
+
+    return NO_ERROR;
+}
+#endif
+
 status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width,
         uint32_t height) {
     ATRACE_CALL();
@@ -679,6 +726,28 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueConsumer::allowUnlimitedSlots(bool allowUnlimitedSlots) {
+    ATRACE_CALL();
+    BQ_LOGV("allowUnlimitedSlots: %d", allowUnlimitedSlots);
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mIsAbandoned) {
+        BQ_LOGE("allowUnlimitedSlots: BufferQueue has been abandoned");
+        return NO_INIT;
+    }
+
+    if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
+        BQ_LOGE("allowUnlimitedSlots: BufferQueue already connected");
+        return INVALID_OPERATION;
+    }
+
+    mCore->mAllowExtendedSlotCount = allowUnlimitedSlots;
+
+    return OK;
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+
 status_t BufferQueueConsumer::setMaxBufferCount(int bufferCount) {
     ATRACE_CALL();
 
@@ -718,16 +787,23 @@
         int maxAcquiredBuffers) {
     ATRACE_FORMAT("%s(%d)", __func__, maxAcquiredBuffers);
 
-    if (maxAcquiredBuffers < 1 ||
-            maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
-        BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d",
-                maxAcquiredBuffers);
-        return BAD_VALUE;
-    }
-
     sp<IConsumerListener> listener;
     { // Autolock scope
         std::unique_lock<std::mutex> lock(mCore->mMutex);
+
+        // We reserve two slots in order to guarantee that the producer and
+        // consumer can run asynchronously.
+        int maxMaxAcquiredBuffers =
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+                mCore->getTotalSlotCountLocked() - 2;
+#else
+                BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS;
+#endif
+        if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > maxMaxAcquiredBuffers) {
+            BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d", maxAcquiredBuffers);
+            return BAD_VALUE;
+        }
+
         mCore->waitWhileAllocatingLocked(lock);
 
         if (mCore->mIsAbandoned) {
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index 5a09399..6c79904 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -38,6 +38,8 @@
 
 #include <system/window.h>
 
+#include <ui/BufferQueueDefs.h>
+
 namespace android {
 
 // Macros for include BufferQueueCore information in log messages
@@ -97,7 +99,11 @@
         mConnectedProducerListener(),
         mBufferReleasedCbEnabled(false),
         mBufferAttachedCbEnabled(false),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#else
         mSlots(),
+#endif
         mQueue(),
         mFreeSlots(),
         mFreeBuffers(),
@@ -111,6 +117,9 @@
         mDefaultWidth(1),
         mDefaultHeight(1),
         mDefaultBufferDataSpace(HAL_DATASPACE_UNKNOWN),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mAllowExtendedSlotCount(false),
+#endif
         mMaxBufferCount(BufferQueueDefs::NUM_BUFFER_SLOTS),
         mMaxAcquiredBufferCount(1),
         mMaxDequeuedBufferCount(1),
@@ -221,6 +230,14 @@
     }
 }
 
+int BufferQueueCore::getTotalSlotCountLocked() const {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    return mAllowExtendedSlotCount ? mMaxBufferCount : BufferQueueDefs::NUM_BUFFER_SLOTS;
+#else
+    return BufferQueueDefs::NUM_BUFFER_SLOTS;
+#endif
+}
+
 int BufferQueueCore::getMinUndequeuedBufferCountLocked() const {
     // If dequeueBuffer is allowed to error out, we don't have to add an
     // extra buffer.
@@ -253,6 +270,26 @@
     return maxBufferCount;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueCore::extendSlotCountLocked(int size) {
+    int previousSize = (int)mSlots.size();
+    if (previousSize > size) {
+        return BAD_VALUE;
+    }
+    if (previousSize == size) {
+        return NO_ERROR;
+    }
+
+    mSlots.resize(size);
+    for (int i = previousSize; i < size; i++) {
+        mUnusedSlots.push_back(i);
+    }
+
+    mMaxBufferCount = size;
+    return NO_ERROR;
+}
+#endif
+
 void BufferQueueCore::clearBufferSlotLocked(int slot) {
     BQ_LOGV("clearBufferSlotLocked: slot %d", slot);
 
@@ -383,7 +420,7 @@
 void BufferQueueCore::validateConsistencyLocked() const {
     static const useconds_t PAUSE_TIME = 0;
     int allocatedSlots = 0;
-    for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+    for (int slot = 0; slot < getTotalSlotCountLocked(); ++slot) {
         bool isInFreeSlots = mFreeSlots.count(slot) != 0;
         bool isInFreeBuffers =
                 std::find(mFreeBuffers.cbegin(), mFreeBuffers.cend(), slot) !=
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 2e7cef0..c241482 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -40,6 +40,7 @@
 #include <gui/TraceUtils.h>
 #include <private/gui/BufferQueueThreadState.h>
 
+#include <utils/Errors.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
 
@@ -108,9 +109,9 @@
         return NO_INIT;
     }
 
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-        BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
-                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+    int maxSlot = mCore->getTotalSlotCountLocked();
+    if (slot < 0 || slot >= maxSlot) {
+        BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)", slot, maxSlot);
         return BAD_VALUE;
     } else if (!mSlots[slot].mBufferState.isDequeued()) {
         BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
@@ -123,6 +124,49 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueProducer::extendSlotCount(int size) {
+    ATRACE_CALL();
+
+    sp<IConsumerListener> listener;
+    {
+        std::lock_guard<std::mutex> lock(mCore->mMutex);
+        BQ_LOGV("extendSlotCount: size %d", size);
+
+        if (mCore->mIsAbandoned) {
+            BQ_LOGE("extendSlotCount: BufferQueue has been abandoned");
+            return NO_INIT;
+        }
+
+        if (!mCore->mAllowExtendedSlotCount) {
+            BQ_LOGE("extendSlotCount: Consumer did not allow unlimited slots");
+            return INVALID_OPERATION;
+        }
+
+        int maxBeforeExtension = mCore->mMaxBufferCount;
+
+        if (size == maxBeforeExtension) {
+            return NO_ERROR;
+        }
+
+        if (size < maxBeforeExtension) {
+            return BAD_VALUE;
+        }
+
+        if (status_t ret = mCore->extendSlotCountLocked(size); ret != OK) {
+            return ret;
+        }
+        listener = mCore->mConsumerListener;
+    }
+
+    if (listener) {
+        listener->onSlotCountChanged(size);
+    }
+
+    return NO_ERROR;
+}
+#endif
+
 status_t BufferQueueProducer::setMaxDequeuedBufferCount(
         int maxDequeuedBuffers) {
     int maxBufferCount;
@@ -170,9 +214,10 @@
         int bufferCount = mCore->getMinUndequeuedBufferCountLocked();
         bufferCount += maxDequeuedBuffers;
 
-        if (bufferCount > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+        if (bufferCount > mCore->getTotalSlotCountLocked()) {
             BQ_LOGE("setMaxDequeuedBufferCount: bufferCount %d too large "
-                    "(max %d)", bufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS);
+                    "(max %d)",
+                    bufferCount, mCore->getTotalSlotCountLocked());
             return BAD_VALUE;
         }
 
@@ -756,9 +801,9 @@
             return BAD_VALUE;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
-                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isDequeued()) {
             // TODO(http://b/140581935): This message is BQ_LOGW because it
@@ -993,9 +1038,9 @@
             return NO_INIT;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)",
-                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isDequeued()) {
             BQ_LOGE("queueBuffer: slot %d is not owned by the producer "
@@ -1239,9 +1284,9 @@
             return BAD_VALUE;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot,
-                    BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isDequeued()) {
             BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
@@ -1409,6 +1454,9 @@
             output->nextFrameNumber = mCore->mFrameCounter + 1;
             output->bufferReplaced = false;
             output->maxBufferCount = mCore->mMaxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            output->isSlotExpansionAllowed = mCore->mAllowExtendedSlotCount;
+#endif
 
             if (listener != nullptr) {
                 // Set up a death notification so that we can disconnect
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index 602bba8..504509d 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -37,6 +37,8 @@
 
 #include <private/gui/ComposerService.h>
 
+#include <ui/BufferQueueDefs.h>
+
 #include <log/log.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
@@ -59,7 +61,11 @@
     return android_atomic_inc(&globalCounter);
 }
 
-ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
+ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp)
+      :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mAbandoned(false),
         mConsumer(bufferQueue),
         mPrevFinalReleaseFence(Fence::NO_FENCE) {
@@ -68,7 +74,12 @@
 
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 ConsumerBase::ConsumerBase(bool controlledByApp, bool consumerIsSurfaceFlinger)
-      : mAbandoned(false), mPrevFinalReleaseFence(Fence::NO_FENCE) {
+      :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+        mAbandoned(false),
+        mPrevFinalReleaseFence(Fence::NO_FENCE) {
     sp<IGraphicBufferProducer> producer;
     BufferQueue::createBufferQueue(&producer, &mConsumer, consumerIsSurfaceFlinger);
     mSurface = sp<Surface>::make(producer, controlledByApp);
@@ -77,7 +88,11 @@
 
 ConsumerBase::ConsumerBase(const sp<IGraphicBufferProducer>& producer,
                            const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp)
-      : mAbandoned(false),
+      :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+        mAbandoned(false),
         mConsumer(consumer),
         mSurface(sp<Surface>::make(producer, controlledByApp)),
         mPrevFinalReleaseFence(Fence::NO_FENCE) {
@@ -101,9 +116,16 @@
     if (err != NO_ERROR) {
         CB_LOGE("ConsumerBase: error connecting to BufferQueue: %s (%d)",
                 strerror(-err), err);
-    } else {
-        mConsumer->setConsumerName(mName);
+        return;
     }
+
+    mConsumer->setConsumerName(mName);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (err = mConsumer->allowUnlimitedSlots(true); err != NO_ERROR) {
+        CB_LOGE("ConsumerBase: error marking as allowed to have unlimited slots: %s (%d)",
+                strerror(-err), err);
+    }
+#endif
 }
 
 ConsumerBase::~ConsumerBase() {
@@ -130,7 +152,11 @@
     }
 
     uint64_t id = buffer->getId();
-    for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); ++i) {
+#else
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+#endif
         auto& slot = mSlots[i];
         if (slot.mGraphicBuffer && slot.mGraphicBuffer->getId() == id) {
             return i;
@@ -242,6 +268,15 @@
         return;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<bool> mask;
+    mConsumer->getReleasedBuffersExtended(&mask);
+    for (size_t i = 0; i < mSlots.size(); i++) {
+        if (mask[i]) {
+            freeBufferLocked(i);
+        }
+    }
+#else
     uint64_t mask = 0;
     mConsumer->getReleasedBuffers(&mask);
     for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
@@ -249,11 +284,23 @@
             freeBufferLocked(i);
         }
     }
+#endif
 }
 
 void ConsumerBase::onSidebandStreamChanged() {
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void ConsumerBase::onSlotCountChanged(int slotCount) {
+    CB_LOGV("onSlotCountChanged: %d", slotCount);
+    Mutex::Autolock lock(mMutex);
+
+    if (slotCount > (int)mSlots.size()) {
+        mSlots.resize(slotCount);
+    }
+}
+#endif
+
 void ConsumerBase::abandon() {
     CB_LOGV("abandon");
     Mutex::Autolock lock(mMutex);
@@ -270,7 +317,11 @@
         CB_LOGE("abandonLocked: ConsumerBase is abandoned!");
         return;
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); ++i) {
+#else
     for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+#endif
         freeBufferLocked(i);
     }
     // disconnect from the BufferQueue
@@ -387,6 +438,15 @@
         CB_LOGE("setMaxBufferCount: ConsumerBase is abandoned!");
         return NO_INIT;
     }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (status_t err = mConsumer->allowUnlimitedSlots(false); err != NO_ERROR) {
+        CB_LOGE("ConsumerBase: error marking as not allowed to have unlimited slots: %s (%d)",
+                strerror(-err), err);
+        return err;
+    }
+#endif
+
     return mConsumer->setMaxBufferCount(bufferCount);
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
@@ -448,6 +508,15 @@
     if (err != OK) {
         return err;
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<bool> mask;
+    mConsumer->getReleasedBuffersExtended(&mask);
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+        if (mask[i]) {
+            freeBufferLocked(i);
+        }
+    }
+#else
     uint64_t mask;
     mConsumer->getReleasedBuffers(&mask);
     for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
@@ -455,6 +524,8 @@
             freeBufferLocked(i);
         }
     }
+#endif
+
     return OK;
 }
 
@@ -596,6 +667,9 @@
     // buffer on the same slot), the buffer producer is definitely no longer
     // tracking it.
     if (!stillTracking(slot, graphicBuffer)) {
+        CB_LOGV("releaseBufferLocked: Not tracking, exiting without calling releaseBuffer for "
+                "slot=%d/%" PRIu64,
+                slot, mSlots[slot].mFrameNumber);
         return OK;
     }
 
@@ -615,7 +689,11 @@
 
 bool ConsumerBase::stillTracking(int slot,
         const sp<GraphicBuffer> graphicBuffer) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (slot < 0 || slot >= (int)mSlots.size()) {
+#else
     if (slot < 0 || slot >= BufferQueue::NUM_BUFFER_SLOTS) {
+#endif
         return false;
     }
     return (mSlots[slot].mGraphicBuffer != nullptr &&
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 5c4879c..1b2354e 100644
--- a/libs/gui/FrameRateUtils.cpp
+++ b/libs/gui/FrameRateUtils.cpp
@@ -42,7 +42,7 @@
 
     if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
         compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST &&
         (!privileged ||
          (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
           compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index f2173cd..168129b 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -119,6 +119,9 @@
         mTexTarget(texTarget),
         mEglDisplay(EGL_NO_DISPLAY),
         mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mAttached(true) {
     GLC_LOGV("GLConsumer");
@@ -129,27 +132,29 @@
 }
 #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),
-    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)
-{
+GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, 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(tex),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mEglDisplay(EGL_NO_DISPLAY),
+        mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mAttached(true) {
     GLC_LOGV("GLConsumer");
 
     memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(),
@@ -176,6 +181,9 @@
         mTexTarget(texTarget),
         mEglDisplay(EGL_NO_DISPLAY),
         mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mAttached(false) {
     GLC_LOGV("GLConsumer");
@@ -204,6 +212,9 @@
         mTexTarget(texTarget),
         mEglDisplay(EGL_NO_DISPLAY),
         mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mAttached(false) {
     GLC_LOGV("GLConsumer");
@@ -395,6 +406,17 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void GLConsumer::onSlotCountChanged(int slotCount) {
+    ConsumerBase::onSlotCountChanged(slotCount);
+
+    Mutex::Autolock lock(mMutex);
+    if (slotCount > (int)mEglSlots.size()) {
+        mEglSlots.resize(slotCount);
+    }
+}
+#endif
+
 status_t GLConsumer::releaseBufferLocked(int buf,
         sp<GraphicBuffer> graphicBuffer,
         EGLDisplay display, EGLSyncKHR eglFence) {
diff --git a/libs/gui/IConsumerListener.cpp b/libs/gui/IConsumerListener.cpp
index f3bd90c..939db59 100644
--- a/libs/gui/IConsumerListener.cpp
+++ b/libs/gui/IConsumerListener.cpp
@@ -31,7 +31,8 @@
     ON_FRAME_DEQUEUED,
     ON_FRAME_CANCELLED,
     ON_FRAME_DETACHED,
-    LAST = ON_FRAME_DETACHED,
+    ON_SLOT_COUNT_CHANGED,
+    LAST = ON_SLOT_COUNT_CHANGED,
 };
 
 } // Anonymous namespace
@@ -85,6 +86,14 @@
                                   FrameEventHistoryDelta* /*outDelta*/) override {
         LOG_ALWAYS_FATAL("IConsumerListener::addAndGetFrameTimestamps cannot be proxied");
     }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    void onSlotCountChanged(int slotCount) override {
+        callRemoteAsync<
+                decltype(&IConsumerListener::onSlotCountChanged)>(Tag::ON_SLOT_COUNT_CHANGED,
+                                                                  slotCount);
+    }
+#endif
 };
 
 // Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
@@ -116,6 +125,13 @@
             return callLocalAsync(data, reply, &IConsumerListener::onFrameCancelled);
         case Tag::ON_FRAME_DETACHED:
             return callLocalAsync(data, reply, &IConsumerListener::onFrameDetached);
+        case Tag::ON_SLOT_COUNT_CHANGED: {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            return callLocalAsync(data, reply, &IConsumerListener::onSlotCountChanged);
+#else
+            return INVALID_OPERATION;
+#endif
+        }
     }
 }
 
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index 282957b..c1b6568 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -16,6 +16,7 @@
 
 #include <gui/IGraphicBufferConsumer.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
 #include <gui/IConsumerListener.h>
 
@@ -24,6 +25,7 @@
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 
+#include <utils/Errors.h>
 #include <utils/NativeHandle.h>
 #include <utils/String8.h>
 #include <cstdint>
@@ -53,7 +55,9 @@
     GET_OCCUPANCY_HISTORY,
     DISCARD_FREE_BUFFERS,
     DUMP_STATE,
-    LAST = DUMP_STATE,
+    ALLOW_UNLIMITED_SLOTS,
+    GET_RELEASED_BUFFERS_EXTENDED,
+    LAST = GET_RELEASED_BUFFERS_EXTENDED,
 };
 
 } // Anonymous namespace
@@ -104,11 +108,25 @@
         return callRemote<Signature>(Tag::GET_RELEASED_BUFFERS, slotMask);
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    status_t getReleasedBuffersExtended(std::vector<bool>* slotMask) override {
+        using Signature = decltype(&IGraphicBufferConsumer::getReleasedBuffersExtended);
+        return callRemote<Signature>(Tag::GET_RELEASED_BUFFERS_EXTENDED, slotMask);
+    }
+#endif
+
     status_t setDefaultBufferSize(uint32_t width, uint32_t height) override {
         using Signature = decltype(&IGraphicBufferConsumer::setDefaultBufferSize);
         return callRemote<Signature>(Tag::SET_DEFAULT_BUFFER_SIZE, width, height);
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    status_t allowUnlimitedSlots(bool allowUnlimitedSlots) override {
+        using Signature = decltype(&IGraphicBufferConsumer::allowUnlimitedSlots);
+        return callRemote<Signature>(Tag::ALLOW_UNLIMITED_SLOTS, allowUnlimitedSlots);
+    }
+#endif
+
     status_t setMaxBufferCount(int bufferCount) override {
         using Signature = decltype(&IGraphicBufferConsumer::setMaxBufferCount);
         return callRemote<Signature>(Tag::SET_MAX_BUFFER_COUNT, bufferCount);
@@ -228,6 +246,20 @@
             using Signature = status_t (IGraphicBufferConsumer::*)(const String8&, String8*) const;
             return callLocal<Signature>(data, reply, &IGraphicBufferConsumer::dumpState);
         }
+        case Tag::GET_RELEASED_BUFFERS_EXTENDED: {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            return callLocal(data, reply, &IGraphicBufferConsumer::getReleasedBuffersExtended);
+#else
+            return INVALID_OPERATION;
+#endif
+        }
+        case Tag::ALLOW_UNLIMITED_SLOTS: {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            return callLocal(data, reply, &IGraphicBufferConsumer::allowUnlimitedSlots);
+#else
+            return INVALID_OPERATION;
+#endif
+        }
     }
 }
 
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 0914480..9f71eb1 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -81,6 +81,7 @@
     GET_LAST_QUEUED_BUFFER2,
     SET_FRAME_RATE,
     SET_ADDITIONAL_OPTIONS,
+    SET_MAX_BUFER_COUNT_EXTENDED,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -149,6 +150,20 @@
         return result;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    status_t extendSlotCount(int size) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32(size);
+        status_t result = remote()->transact(SET_MAX_BUFER_COUNT_EXTENDED, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.readInt32();
+        return result;
+    }
+#endif
+
     virtual status_t setAsyncMode(bool async) {
         Parcel data, reply;
         data.writeInterfaceToken(
@@ -981,6 +996,14 @@
 
 // ----------------------------------------------------------------------
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t IGraphicBufferProducer::extendSlotCount(int size) {
+    // No-op for IGBP other than BufferQueue.
+    (void)size;
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::setLegacyBufferDrop(bool drop) {
     // No-op for IGBP other than BufferQueue.
     (void) drop;
@@ -1582,6 +1605,15 @@
             return NO_ERROR;
         }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        case SET_MAX_BUFER_COUNT_EXTENDED: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            int size = data.readInt32();
+            status_t result = extendSlotCount(size);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp
index 4e92a39..8b2e2dd 100644
--- a/libs/gui/IGraphicBufferProducerFlattenables.cpp
+++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp
@@ -128,7 +128,7 @@
 constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
     return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
             sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount) +
-            sizeof(result);
+            sizeof(result) + sizeof(isSlotExpansionAllowed);
 }
 size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
     return minFlattenedSize() + frameTimestamps.getFlattenedSize();
@@ -152,6 +152,7 @@
     FlattenableUtils::write(buffer, size, nextFrameNumber);
     FlattenableUtils::write(buffer, size, bufferReplaced);
     FlattenableUtils::write(buffer, size, maxBufferCount);
+    FlattenableUtils::write(buffer, size, isSlotExpansionAllowed);
 
     status_t result = frameTimestamps.flatten(buffer, size, fds, count);
     if (result != NO_ERROR) {
@@ -175,6 +176,7 @@
     FlattenableUtils::read(buffer, size, nextFrameNumber);
     FlattenableUtils::read(buffer, size, bufferReplaced);
     FlattenableUtils::read(buffer, size, maxBufferCount);
+    FlattenableUtils::read(buffer, size, isSlotExpansionAllowed);
 
     status_t result = frameTimestamps.unflatten(buffer, size, fds, count);
     if (result != NO_ERROR) {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index c1a03fc..44aac9b 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -66,6 +66,8 @@
         mask(0),
         reserved(0),
         cornerRadius(0.0f),
+        clientDrawnCornerRadius(0.0f),
+        clientDrawnShadowRadius(0.0f),
         backgroundBlurRadius(0),
         color(0),
         bufferTransform(0),
@@ -140,6 +142,8 @@
 
     SAFE_PARCEL(output.write, colorTransform.asArray(), 16 * sizeof(float));
     SAFE_PARCEL(output.writeFloat, cornerRadius);
+    SAFE_PARCEL(output.writeFloat, clientDrawnCornerRadius);
+    SAFE_PARCEL(output.writeFloat, clientDrawnShadowRadius);
     SAFE_PARCEL(output.writeUint32, backgroundBlurRadius);
     SAFE_PARCEL(output.writeParcelable, metadata);
     SAFE_PARCEL(output.writeFloat, bgColor.r);
@@ -274,6 +278,8 @@
 
     SAFE_PARCEL(input.read, &colorTransform, 16 * sizeof(float));
     SAFE_PARCEL(input.readFloat, &cornerRadius);
+    SAFE_PARCEL(input.readFloat, &clientDrawnCornerRadius);
+    SAFE_PARCEL(input.readFloat, &clientDrawnShadowRadius);
     SAFE_PARCEL(input.readUint32, &backgroundBlurRadius);
     SAFE_PARCEL(input.readParcelable, &metadata);
 
@@ -596,6 +602,14 @@
         what |= eCornerRadiusChanged;
         cornerRadius = other.cornerRadius;
     }
+    if (other.what & eClientDrawnCornerRadiusChanged) {
+        what |= eClientDrawnCornerRadiusChanged;
+        clientDrawnCornerRadius = other.clientDrawnCornerRadius;
+    }
+    if (other.what & eClientDrawnShadowsChanged) {
+        what |= eClientDrawnShadowsChanged;
+        clientDrawnShadowRadius = other.clientDrawnShadowRadius;
+    }
     if (other.what & eBackgroundBlurRadiusChanged) {
         what |= eBackgroundBlurRadiusChanged;
         backgroundBlurRadius = other.backgroundBlurRadius;
@@ -809,6 +823,8 @@
     }
     CHECK_DIFF(diff, eLayerStackChanged, other, layerStack);
     CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius);
+    CHECK_DIFF(diff, eClientDrawnCornerRadiusChanged, other, clientDrawnCornerRadius);
+    CHECK_DIFF(diff, eClientDrawnShadowsChanged, other, clientDrawnShadowRadius);
     CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius);
     if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged;
     if (other.what & eRelativeLayerChanged) {
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index e41f9bb..ec23365 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -38,6 +38,7 @@
 #include <utils/NativeHandle.h>
 #include <utils/Trace.h>
 
+#include <ui/BufferQueueDefs.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
@@ -98,7 +99,10 @@
       : mGraphicBufferProducer(bufferProducer),
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
         mSurfaceDeathListener(nullptr),
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(NUM_BUFFER_SLOTS),
+#endif
         mCrop(Rect::EMPTY_RECT),
         mBufferAge(0),
         mGenerationNumber(0),
@@ -192,7 +196,7 @@
 status_t Surface::allowAllocation(bool allowAllocation) {
     return mGraphicBufferProducer->allowAllocation(allowAllocation);
 }
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#endif
 
 status_t Surface::setGenerationNumber(uint32_t generation) {
     status_t result = mGraphicBufferProducer->setGenerationNumber(generation);
@@ -658,7 +662,11 @@
         return result;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (buf < 0 || buf >= (int)mSlots.size()) {
+#else
     if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
+#endif
         ALOGE("dequeueBuffer: IGraphicBufferProducer returned invalid slot number %d", buf);
         android_errorWriteLog(0x534e4554, "36991414"); // SafetyNet logging
         return FAILED_TRANSACTION;
@@ -757,7 +765,11 @@
     Mutex::Autolock lock(mMutex);
 
     uint64_t bufferId = buffer->getId();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int slot = 0; slot < (int)mSlots.size(); ++slot) {
+#else
     for (int slot = 0; slot < Surface::NUM_BUFFER_SLOTS; ++slot) {
+#endif
         auto& bufferSlot = mSlots[slot];
         if (bufferSlot.buffer != nullptr && bufferSlot.buffer->getId() == bufferId) {
             bufferSlot.buffer = nullptr;
@@ -840,7 +852,11 @@
             return output.result;
         }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        if (output.slot < 0 || output.slot >= (int)mSlots.size()) {
+#else
         if (output.slot < 0 || output.slot >= NUM_BUFFER_SLOTS) {
+#endif
             mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
             ALOGE("%s: IGraphicBufferProducer returned invalid slot number %d",
                     __FUNCTION__, output.slot);
@@ -1027,7 +1043,11 @@
         return BAD_VALUE;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
         if (mSlots[i].buffer != nullptr &&
                 mSlots[i].buffer->handle == buffer->handle) {
             return i;
@@ -2094,6 +2114,9 @@
         mDefaultHeight = output.height;
         mNextFrameNumber = output.nextFrameNumber;
         mMaxBufferCount = output.maxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mIsSlotExpansionAllowed = output.isSlotExpansionAllowed;
+#endif
 
         // Ignore transform hint if sticky transform is set or transform to display inverse flag is
         // set. Transform hint should be ignored if the client is expected to always submit buffers
@@ -2190,7 +2213,11 @@
         *outFence = Fence::NO_FENCE;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
         if (mSlots[i].buffer != nullptr &&
                 mSlots[i].buffer->getId() == buffer->getId()) {
             if (mReportRemovedBuffers) {
@@ -2292,8 +2319,35 @@
     ALOGV("Surface::setMaxDequeuedBufferCount");
     Mutex::Autolock lock(mMutex);
 
-    status_t err = mGraphicBufferProducer->setMaxDequeuedBufferCount(
-            maxDequeuedBuffers);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (maxDequeuedBuffers > BufferQueueDefs::NUM_BUFFER_SLOTS && !mIsSlotExpansionAllowed) {
+        return BAD_VALUE;
+    }
+
+    int minUndequeuedBuffers = 0;
+    status_t err = mGraphicBufferProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+                                                 &minUndequeuedBuffers);
+    if (err != OK) {
+        ALOGE("IGraphicBufferProducer::query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS) returned %s",
+              strerror(-err));
+        return err;
+    }
+
+    if (maxDequeuedBuffers > (int)mSlots.size()) {
+        int newSlotCount = minUndequeuedBuffers + maxDequeuedBuffers;
+        err = mGraphicBufferProducer->extendSlotCount(newSlotCount);
+        if (err != OK) {
+            ALOGE("IGraphicBufferProducer::extendSlotCount(%d) returned %s", newSlotCount,
+                  strerror(-err));
+            return err;
+        }
+
+        mSlots.resize(newSlotCount);
+    }
+    err = mGraphicBufferProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+#else
+    status_t err = mGraphicBufferProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+#endif
     ALOGE_IF(err, "IGraphicBufferProducer::setMaxDequeuedBufferCount(%d) "
             "returned %s", maxDequeuedBuffers, strerror(-err));
 
@@ -2501,7 +2555,11 @@
         ALOGE("%s: %zu buffers were freed while being dequeued!",
                 __FUNCTION__, mDequeuedSlots.size());
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
         mSlots[i].buffer = nullptr;
     }
 }
@@ -2510,7 +2568,11 @@
         std::vector<sp<GraphicBuffer>>* outBuffers) {
     ALOGV("Surface::getAndFlushBuffersFromSlots");
     for (int32_t i : slots) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        if (i < 0 || i >= (int)mSlots.size()) {
+#else
         if (i < 0 || i >= NUM_BUFFER_SLOTS) {
+#endif
             ALOGE("%s: Invalid slotIndex: %d", __FUNCTION__, i);
             return BAD_VALUE;
         }
@@ -2670,7 +2732,11 @@
             newDirtyRegion.set(bounds);
             mDirtyRegion.clear();
             Mutex::Autolock lock(mMutex);
-            for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
+            for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
                 mSlots[i].dirtyRegion.clear();
             }
         }
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index cabde22..c6ba7d8 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -829,9 +829,7 @@
 
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
       : mId(other.mId),
-        mAnimation(other.mAnimation),
-        mEarlyWakeupStart(other.mEarlyWakeupStart),
-        mEarlyWakeupEnd(other.mEarlyWakeupEnd),
+        mFlags(other.mFlags),
         mMayContainBuffer(other.mMayContainBuffer),
         mDesiredPresentTime(other.mDesiredPresentTime),
         mIsAutoTimestamp(other.mIsAutoTimestamp),
@@ -868,9 +866,7 @@
 
 status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) {
     const uint64_t transactionId = parcel->readUint64();
-    const bool animation = parcel->readBool();
-    const bool earlyWakeupStart = parcel->readBool();
-    const bool earlyWakeupEnd = parcel->readBool();
+    const uint32_t flags = parcel->readUint32();
     const int64_t desiredPresentTime = parcel->readInt64();
     const bool isAutoTimestamp = parcel->readBool();
     const bool logCallPoints = parcel->readBool();
@@ -965,9 +961,7 @@
 
     // Parsing was successful. Update the object.
     mId = transactionId;
-    mAnimation = animation;
-    mEarlyWakeupStart = earlyWakeupStart;
-    mEarlyWakeupEnd = earlyWakeupEnd;
+    mFlags = flags;
     mDesiredPresentTime = desiredPresentTime;
     mIsAutoTimestamp = isAutoTimestamp;
     mFrameTimelineInfo = frameTimelineInfo;
@@ -996,9 +990,7 @@
     const_cast<SurfaceComposerClient::Transaction*>(this)->cacheBuffers();
 
     parcel->writeUint64(mId);
-    parcel->writeBool(mAnimation);
-    parcel->writeBool(mEarlyWakeupStart);
-    parcel->writeBool(mEarlyWakeupEnd);
+    parcel->writeUint32(mFlags);
     parcel->writeInt64(mDesiredPresentTime);
     parcel->writeBool(mIsAutoTimestamp);
     parcel->writeBool(mLogCallPoints);
@@ -1131,8 +1123,7 @@
     mInputWindowCommands.merge(other.mInputWindowCommands);
 
     mMayContainBuffer |= other.mMayContainBuffer;
-    mEarlyWakeupStart = mEarlyWakeupStart || other.mEarlyWakeupStart;
-    mEarlyWakeupEnd = mEarlyWakeupEnd || other.mEarlyWakeupEnd;
+    mFlags |= other.mFlags;
     mApplyToken = other.mApplyToken;
 
     mergeFrameTimelineInfo(mFrameTimelineInfo, other.mFrameTimelineInfo);
@@ -1154,15 +1145,13 @@
     mInputWindowCommands.clear();
     mUncacheBuffers.clear();
     mMayContainBuffer = false;
-    mAnimation = false;
-    mEarlyWakeupStart = false;
-    mEarlyWakeupEnd = false;
     mDesiredPresentTime = 0;
     mIsAutoTimestamp = true;
     mFrameTimelineInfo = {};
     mApplyToken = nullptr;
     mMergedTransactionIds.clear();
     mLogCallPoints = false;
+    mFlags = 0;
 }
 
 uint64_t SurfaceComposerClient::Transaction::getId() {
@@ -1325,7 +1314,6 @@
 
     Vector<ComposerState> composerStates;
     Vector<DisplayState> displayStates;
-    uint32_t flags = 0;
 
     for (auto const& kv : mComposerStates) {
         composerStates.add(kv.second);
@@ -1333,32 +1321,26 @@
 
     displayStates = std::move(mDisplayStates);
 
-    if (mAnimation) {
-        flags |= ISurfaceComposer::eAnimation;
-    }
     if (oneWay) {
         if (synchronous) {
             ALOGE("Transaction attempted to set synchronous and one way at the same time"
                   " this is an invalid request. Synchronous will win for safety");
         } else {
-            flags |= ISurfaceComposer::eOneWay;
+            mFlags |= ISurfaceComposer::eOneWay;
         }
     }
 
-    // If both mEarlyWakeupStart and mEarlyWakeupEnd are set
+    // If both ISurfaceComposer::eEarlyWakeupStart and ISurfaceComposer::eEarlyWakeupEnd are set
     // it is equivalent for none
-    if (mEarlyWakeupStart && !mEarlyWakeupEnd) {
-        flags |= ISurfaceComposer::eEarlyWakeupStart;
+    uint32_t wakeupFlags = ISurfaceComposer::eEarlyWakeupStart | ISurfaceComposer::eEarlyWakeupEnd;
+    if ((mFlags & wakeupFlags) == wakeupFlags) {
+        mFlags &= ~(wakeupFlags);
     }
-    if (mEarlyWakeupEnd && !mEarlyWakeupStart) {
-        flags |= ISurfaceComposer::eEarlyWakeupEnd;
-    }
-
     sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
 
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
     status_t binderStatus =
-            sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags,
+            sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, mFlags,
                                     applyToken, mInputWindowCommands, mDesiredPresentTime,
                                     mIsAutoTimestamp, mUncacheBuffers, hasListenerCallbacks,
                                     listenerCallbacks, mId, mMergedTransactionIds);
@@ -1461,15 +1443,15 @@
 }
 
 void SurfaceComposerClient::Transaction::setAnimationTransaction() {
-    mAnimation = true;
+    mFlags |= ISurfaceComposer::eAnimation;
 }
 
 void SurfaceComposerClient::Transaction::setEarlyWakeupStart() {
-    mEarlyWakeupStart = true;
+    mFlags |= ISurfaceComposer::eEarlyWakeupStart;
 }
 
 void SurfaceComposerClient::Transaction::setEarlyWakeupEnd() {
-    mEarlyWakeupEnd = true;
+    mFlags |= ISurfaceComposer::eEarlyWakeupEnd;
 }
 
 layer_state_t* SurfaceComposerClient::Transaction::getLayerState(const sp<SurfaceControl>& sc) {
@@ -1695,6 +1677,29 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setClientDrawnCornerRadius(
+        const sp<SurfaceControl>& sc, float clientDrawnCornerRadius) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eClientDrawnCornerRadiusChanged;
+    s->clientDrawnCornerRadius = clientDrawnCornerRadius;
+    return *this;
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setClientDrawnShadowRadius(
+        const sp<SurfaceControl>& sc, float clientDrawnShadowRadius) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eClientDrawnShadowsChanged;
+    s->clientDrawnShadowRadius = clientDrawnShadowRadius;
+    return *this;
+}
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackgroundBlurRadius(
         const sp<SurfaceControl>& sc, int backgroundBlurRadius) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 82d2554..3fb66d1 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -59,6 +59,32 @@
     return out;
 }
 
+status_t writeTransform(android::Parcel* parcel, const ui::Transform& transform) {
+    return parcel->writeFloat(transform.dsdx()) ?:
+            parcel->writeFloat(transform.dtdx()) ?:
+            parcel->writeFloat(transform.tx()) ?:
+            parcel->writeFloat(transform.dtdy()) ?:
+            parcel->writeFloat(transform.dsdy()) ?:
+            parcel->writeFloat(transform.ty());
+}
+
+status_t readTransform(const android::Parcel* parcel, ui::Transform& transform) {
+    float dsdx, dtdx, tx, dtdy, dsdy, ty;
+
+    const status_t status = parcel->readFloat(&dsdx) ?:
+            parcel->readFloat(&dtdx) ?:
+            parcel->readFloat(&tx) ?:
+            parcel->readFloat(&dtdy) ?:
+            parcel->readFloat(&dsdy) ?:
+            parcel->readFloat(&ty);
+    if (status != OK) {
+        return status;
+    }
+
+    transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
+    return OK;
+}
+
 } // namespace
 
 void WindowInfo::setInputConfig(ftl::Flags<InputConfig> config, bool value) {
@@ -73,10 +99,6 @@
     touchableRegion.orSelf(region);
 }
 
-bool WindowInfo::supportsSplitTouch() const {
-    return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
-}
-
 bool WindowInfo::isSpy() const {
     return inputConfig.test(InputConfig::SPY);
 }
@@ -135,12 +157,7 @@
         parcel->writeInt32(surfaceInset) ?:
         parcel->writeFloat(globalScaleFactor) ?:
         parcel->writeFloat(alpha) ?:
-        parcel->writeFloat(transform.dsdx()) ?:
-        parcel->writeFloat(transform.dtdx()) ?:
-        parcel->writeFloat(transform.tx()) ?:
-        parcel->writeFloat(transform.dtdy()) ?:
-        parcel->writeFloat(transform.dsdy()) ?:
-        parcel->writeFloat(transform.ty()) ?:
+        writeTransform(parcel, transform) ?:
         parcel->writeInt32(static_cast<int32_t>(touchOcclusionMode)) ?:
         parcel->writeInt32(ownerPid.val()) ?:
         parcel->writeInt32(ownerUid.val()) ?:
@@ -153,8 +170,12 @@
         parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?:
         parcel->writeStrongBinder(windowToken) ?:
         parcel->writeStrongBinder(focusTransferTarget) ?:
-        parcel->writeBool(canOccludePresentation);
+        parcel->writeBool(canOccludePresentation) ?:
+        parcel->writeBool(cloneLayerStackTransform.has_value());
     // clang-format on
+    if (cloneLayerStackTransform) {
+        status = status ?: writeTransform(parcel, *cloneLayerStackTransform);
+    }
     return status;
 }
 
@@ -174,10 +195,10 @@
         return status;
     }
 
-    float dsdx, dtdx, tx, dtdy, dsdy, ty;
     int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt,
             displayIdInt;
     sp<IBinder> touchableRegionCropHandleSp;
+    bool hasCloneLayerStackTransform = false;
 
     // clang-format off
     status = parcel->readInt32(&lpFlags) ?:
@@ -188,12 +209,7 @@
         parcel->readInt32(&surfaceInset) ?:
         parcel->readFloat(&globalScaleFactor) ?:
         parcel->readFloat(&alpha) ?:
-        parcel->readFloat(&dsdx) ?:
-        parcel->readFloat(&dtdx) ?:
-        parcel->readFloat(&tx) ?:
-        parcel->readFloat(&dtdy) ?:
-        parcel->readFloat(&dsdy) ?:
-        parcel->readFloat(&ty) ?:
+        readTransform(parcel, /*byRef*/ transform) ?:
         parcel->readInt32(&touchOcclusionModeInt) ?:
         parcel->readInt32(&ownerPidInt) ?:
         parcel->readInt32(&ownerUidInt) ?:
@@ -206,8 +222,8 @@
         parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
         parcel->readNullableStrongBinder(&windowToken) ?:
         parcel->readNullableStrongBinder(&focusTransferTarget) ?:
-        parcel->readBool(&canOccludePresentation);
-
+        parcel->readBool(&canOccludePresentation)?:
+        parcel->readBool(&hasCloneLayerStackTransform);
     // clang-format on
 
     if (status != OK) {
@@ -216,7 +232,6 @@
 
     layoutParamsFlags = ftl::Flags<Flag>(lpFlags);
     layoutParamsType = static_cast<Type>(lpType);
-    transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
     touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
     inputConfig = ftl::Flags<InputConfig>(inputConfigInt);
     ownerPid = Pid{ownerPidInt};
@@ -224,6 +239,15 @@
     touchableRegionCropHandle = touchableRegionCropHandleSp;
     displayId = ui::LogicalDisplayId{displayIdInt};
 
+    cloneLayerStackTransform =
+            hasCloneLayerStackTransform ? std::make_optional<ui::Transform>() : std::nullopt;
+    if (cloneLayerStackTransform) {
+        status = readTransform(parcel, /*byRef*/ *cloneLayerStackTransform);
+        if (status != OK) {
+            return status;
+        }
+    }
+
     return OK;
 }
 
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 0948c4d0..f1c75d3 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -76,6 +76,9 @@
         void onSetFrameRate(float frameRate, int8_t compatibility,
                             int8_t changeFrameRateStrategy) override;
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        void onSlotCountChanged(int slotCount) override;
+#endif
     private:
         // mConsumerListener is a weak reference to the IConsumerListener.  This is
         // the raison d'etre of ProxyConsumerListener.
diff --git a/libs/gui/include/gui/BufferQueueConsumer.h b/libs/gui/include/gui/BufferQueueConsumer.h
index 6aa801a..e00c44e 100644
--- a/libs/gui/include/gui/BufferQueueConsumer.h
+++ b/libs/gui/include/gui/BufferQueueConsumer.h
@@ -96,11 +96,26 @@
     // This should be called from the onBuffersReleased() callback.
     virtual status_t getReleasedBuffers(uint64_t* outSlotMask);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // getReleasedBuffers sets the values pointed to by outSlotMask to the bits
+    // indicating which buffer slots have been released by the BufferQueue
+    // but have not yet been released by the consumer.
+    //
+    // This should be called from the onBuffersReleased() callback when
+    // allowUnlimitedSlots has been called.
+    virtual status_t getReleasedBuffersExtended(std::vector<bool>* outSlotMask) override;
+#endif
+
     // setDefaultBufferSize is used to set the size of buffers returned by
     // dequeueBuffer when a width and height of zero is requested.  Default
     // is 1x1.
     virtual status_t setDefaultBufferSize(uint32_t width, uint32_t height);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // see IGraphicBufferConsumer::allowUnlimitedSlots
+    virtual status_t allowUnlimitedSlots(bool allowUnlimitedSlots) override;
+#endif
+
     // see IGraphicBufferConsumer::setMaxBufferCount
     virtual status_t setMaxBufferCount(int bufferCount);
 
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index 77cdf2c..7f92a46 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -32,10 +32,11 @@
 #include <utils/Trace.h>
 #include <utils/Vector.h>
 
-#include <list>
-#include <set>
-#include <mutex>
 #include <condition_variable>
+#include <list>
+#include <mutex>
+#include <set>
+#include <vector>
 
 #define ATRACE_BUFFER_INDEX(index)                                                        \
     do {                                                                                  \
@@ -91,6 +92,10 @@
     // Dump our state in a string
     void dumpState(const String8& prefix, String8* outResult) const;
 
+    // getTotalSlotCountLocked returns the total number of slots in use by the
+    // buffer queue at this time.
+    int getTotalSlotCountLocked() const;
+
     // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
     // that must remain in a state other than DEQUEUED. The async parameter
     // tells whether we're in asynchronous mode.
@@ -120,6 +125,10 @@
     int getMaxBufferCountLocked(bool asyncMode,
             bool dequeueBufferCannotBlock, int maxBufferCount) const;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // This resizes mSlots to the given size, but only if it's increasing.
+    status_t extendSlotCountLocked(int size);
+#endif
     // clearBufferSlotLocked frees the GraphicBuffer and sync resources for the
     // given slot.
     void clearBufferSlotLocked(int slot);
@@ -204,7 +213,7 @@
     // mConnectedProducerListener will not trigger onBufferAttached() callback.
     bool mBufferAttachedCbEnabled;
 
-    // mSlots is an array of buffer slots that must be mirrored on the producer
+    // mSlots is a collection of buffer slots that must be mirrored on the producer
     // side. This allows buffer ownership to be transferred between the producer
     // and consumer without sending a GraphicBuffer over Binder. The entire
     // array is initialized to NULL at construction time, and buffers are
@@ -266,8 +275,14 @@
     // is specified.
     android_dataspace mDefaultBufferDataSpace;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // mAllowExtendedSlotCount is set by the consumer to permit the producer to
+    // request an unlimited number of slots.
+    bool mAllowExtendedSlotCount;
+#endif
+
     // mMaxBufferCount is the limit on the number of buffers that will be
-    // allocated at one time. This limit can be set by the consumer.
+    // allocated at one time.
     int mMaxBufferCount;
 
     // mMaxAcquiredBufferCount is the number of buffers that the consumer may
diff --git a/libs/gui/include/gui/BufferQueueDefs.h b/libs/gui/include/gui/BufferQueueDefs.h
index ffafb49..42cf439 100644
--- a/libs/gui/include/gui/BufferQueueDefs.h
+++ b/libs/gui/include/gui/BufferQueueDefs.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_GUI_BUFFERQUEUECOREDEFS_H
 #define ANDROID_GUI_BUFFERQUEUECOREDEFS_H
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferSlot.h>
 #include <ui/BufferQueueDefs.h>
 
@@ -24,7 +25,11 @@
     class BufferQueueCore;
 
     namespace BufferQueueDefs {
-        typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    typedef std::vector<BufferSlot> SlotsType;
+#else
+    typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+#endif
     } // namespace BufferQueueDefs
 } // namespace android
 
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 086ce7c..50abadb 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -47,6 +47,11 @@
     // flags indicating that previously-returned buffers are no longer valid.
     virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // see IGraphicsBufferProducer::extendSlotCount
+    virtual status_t extendSlotCount(int size) override;
+#endif
+
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount
     virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers);
 
diff --git a/libs/gui/include/gui/ConsumerBase.h b/libs/gui/include/gui/ConsumerBase.h
index e976aa4..5cd19c1 100644
--- a/libs/gui/include/gui/ConsumerBase.h
+++ b/libs/gui/include/gui/ConsumerBase.h
@@ -185,7 +185,9 @@
     virtual void onFrameDetached(const uint64_t bufferId) override;
     virtual void onBuffersReleased() override;
     virtual void onSidebandStreamChanged() override;
-
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    virtual void onSlotCountChanged(int slotCount) override;
+#endif
     virtual int getSlotForBufferLocked(const sp<GraphicBuffer>& buffer);
 
     virtual status_t detachBufferLocked(int slotIndex);
@@ -284,7 +286,11 @@
     // slot that has not yet been used. The buffer allocated to a slot will also
     // be replaced if the requested buffer usage or geometry differs from that
     // of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<Slot> mSlots;
+#else
     Slot mSlots[BufferQueueDefs::NUM_BUFFER_SLOTS];
+#endif
 
     // mAbandoned indicates that the BufferQueue will no longer be used to
     // consume images buffers pushed to it using the IGraphicBufferProducer
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index 8a66dc0..30cbfa2 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -266,6 +266,9 @@
     virtual status_t acquireBufferLocked(BufferItem *item, nsecs_t presentWhen,
             uint64_t maxFrameNumber = 0) override;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    virtual void onSlotCountChanged(int slotCount) override;
+#endif
     // releaseBufferLocked overrides the ConsumerBase method to update the
     // mEglSlots array in addition to the ConsumerBase.
     virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
@@ -496,8 +499,11 @@
     // slot that has not yet been used. The buffer allocated to a slot will also
     // be replaced if the requested buffer usage or geometry differs from that
     // of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<EglSlot> mEglSlots;
+#else
     EglSlot mEglSlots[BufferQueueDefs::NUM_BUFFER_SLOTS];
-
+#endif
     // mCurrentTexture is the buffer slot index of the buffer that is currently
     // bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT,
     // indicating that no buffer slot is currently bound to the texture. Note,
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 51d3959..1695aae 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -98,6 +98,16 @@
     virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
                                 int8_t /*changeFrameRateStrategy*/) {}
 #endif
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // Notifies the consumer that IGraphicBufferProducer::extendSlotCount has
+    // been called and the total slot count has increased.
+    //
+    // This will only ever be called if
+    // IGraphicBufferConsumer::allowUnlimitedSlots has been called on the
+    // consumer.
+    virtual void onSlotCountChanged(int /* slotCount */) {}
+#endif
 };
 
 #ifndef NO_BINDER
diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h
index 18f5488..56eb291 100644
--- a/libs/gui/include/gui/IGraphicBufferConsumer.h
+++ b/libs/gui/include/gui/IGraphicBufferConsumer.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/OccupancyTracker.h>
 
 #include <binder/IInterface.h>
@@ -35,6 +36,10 @@
 class GraphicBuffer;
 class IConsumerListener;
 class NativeHandle;
+
+/*
+ * See IGraphicBufferProducer for details on SLOT_COUNT.
+ */
 #ifndef NO_BINDER
 class IGraphicBufferConsumer : public IInterface {
 public:
@@ -92,7 +97,7 @@
     //
     // Return of a value other than NO_ERROR means an error has occurred:
     // * BAD_VALUE - the given slot number is invalid, either because it is out of the range
-    //               [0, NUM_BUFFER_SLOTS) or because the slot it refers to is not
+    //               [0, SLOT_COUNT) or because the slot it refers to is not
     //               currently acquired.
     virtual status_t detachBuffer(int slot) = 0;
 
@@ -173,6 +178,19 @@
     // * NO_INIT - the BufferQueue has been abandoned.
     virtual status_t getReleasedBuffers(uint64_t* slotMask) = 0;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // getReleasedBuffersExtended for each slot, sets slotMask[slot] to 1 if it
+    // corresponds to a released buffer slot. In particular, a released buffer
+    // is one that has been released by the BufferQueue but has not yet been
+    // released by the consumer.
+    //
+    // This should be called from the onBuffersReleased() callback.
+    //
+    // Return of a value other than NO_ERROR means an error has occurred:
+    // * NO_INIT - the BufferQueue has been abandoned.
+    virtual status_t getReleasedBuffersExtended(std::vector<bool>* slotMask) = 0;
+#endif
+
     // setDefaultBufferSize is used to set the size of buffers returned by dequeueBuffer when a
     // width and height of zero is requested. Default is 1x1.
     //
@@ -180,6 +198,26 @@
     // * BAD_VALUE - either w or h was zero
     virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // allowUnlimitedSlots allows the producer to set the upper bound on slots.
+    //
+    // Must be called before the producer is connected. If the producer
+    // increases the slot count, an IConsumerListener::onSlotCountChanged
+    // update is sent.
+    //
+    // This can not be used with setMaxBufferCount. Calls after
+    // setMaxBufferCount will fail and calls to setMaxBufferCount after setting
+    // this to true will fail.
+    //
+    // Return of a value other than NO_ERROR means an error has occurred:
+    // * NO_INIT - the BufferQueue has been abandoned
+    // * INVALID_OPERATION - one of the following errors has occurred:
+    //                       * Producer has been connected
+    //                       * setMaxBufferCount has been called and shrunk the
+    //                         BufferQueue.
+    virtual status_t allowUnlimitedSlots(bool allowUnlimitedSlots) = 0;
+#endif
+
     // setMaxBufferCount sets the maximum value for the number of buffers used in the BufferQueue
     // (the initial default is NUM_BUFFER_SLOTS). If a call to setMaxAcquiredBufferCount (by the
     // consumer), or a call to setAsyncMode or setMaxDequeuedBufferCount (by the producer), would
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index a42ddc4..7accca6 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -72,6 +72,14 @@
  * dequeueBuffer() to get an empty buffer, fills it with data, then
  * calls queueBuffer() to make it available to the consumer.
  *
+ * BufferQueues have a size, which we'll refer to in other comments as
+ * SLOT_COUNT. Its default is 64 (NUM_BUFFER_SLOTS). It can be adjusted by
+ * the IGraphicBufferConsumer::setMaxBufferCount, or when
+ * IGraphicBufferConsumer::allowUnlimitedSlots is set to true, by
+ * IGraphicBufferProducer::extendSlotCount. The actual number of buffers in use
+ * is a function of various configurations, including whether we're in single
+ * buffer mode, the maximum dequeuable/aquirable buffers, and SLOT_COUNT.
+ *
  * This class was previously called ISurfaceTexture.
  */
 #ifndef NO_BINDER
@@ -106,7 +114,7 @@
     // slot->buffer mapping so that it's not necessary to transfer a
     // GraphicBuffer for every dequeue operation.
     //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    // The slot must be in the range of [0, SLOT_COUNT).
     //
     // Return of a value other than NO_ERROR means an error has occurred:
     // * NO_INIT - the buffer queue has been abandoned or the producer is not
@@ -116,6 +124,30 @@
     //              * buffer specified by the slot is not dequeued
     virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // extendSlotCount sets the maximum slot count (SLOT_COUNT) to the given
+    //  size. This feature must be enabled by the consumer to function via
+    // IGraphicBufferConsumer::allowUnlimitedSlots. This must be called before
+    // the producer connects.
+    //
+    // After calling this, any slot can be returned in the [0, size) range.
+    // Callers are responsible for the allocation of the appropriate slots
+    // array for their own buffer cache.
+    //
+    // On success, the consumer is notified (so that it can increase its own
+    // slot cache).
+    //
+    // Return of a value other than NO_ERROR means that an error has occurred:
+    // * NO_INIT - the buffer queue has been abandoned
+    // * INVALID_OPERATION - one of the following conditions has occurred:
+    //                     *  The producer is connected already
+    //                     *  The consumer didn't call allowUnlimitedSlots
+    // * BAD_VALUE - The value is smaller than the previous max size
+    //               (initialized to 64, then whatever the last call to this
+    //               was)
+    virtual status_t extendSlotCount(int size);
+#endif
+
     // setMaxDequeuedBufferCount sets the maximum number of buffers that can be
     // dequeued by the producer at one time. If this method succeeds, any new
     // buffer slots will be both unallocated and owned by the BufferQueue object
@@ -129,7 +161,7 @@
     // will result in a BAD_VALUE error.
     //
     // The buffer count should be at least 1 (inclusive), but at most
-    // (NUM_BUFFER_SLOTS - the minimum undequeued buffer count) (exclusive). The
+    // (SLOT_COUNT - the minimum undequeued buffer count) (exclusive). The
     // minimum undequeued buffer count can be obtained by calling
     // query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS).
     //
@@ -239,8 +271,8 @@
     // * NO_INIT - the buffer queue has been abandoned or the producer is not
     //             connected.
     // * BAD_VALUE - the given slot number is invalid, either because it is
-    //               out of the range [0, NUM_BUFFER_SLOTS), or because the slot
-    //               it refers to is not currently dequeued and requested.
+    //               out of the range [0, SLOT_COUNT), or because the slot it
+    //               refers to is not currently dequeued and requested.
     virtual status_t detachBuffer(int slot) = 0;
 
     // detachNextBuffer is equivalent to calling dequeueBuffer, requestBuffer,
@@ -415,6 +447,7 @@
         FrameEventHistoryDelta frameTimestamps;
         bool bufferReplaced{false};
         int maxBufferCount{BufferQueueDefs::NUM_BUFFER_SLOTS};
+        bool isSlotExpansionAllowed{false};
         status_t result{NO_ERROR};
     };
 
@@ -430,7 +463,7 @@
     // below). Any other properties (zero point, etc)
     // are client-dependent, and should be documented by the client.
     //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    // The slot must be in the range of [0, SLOT_COUNT).
     //
     // Upon success, the output will be filled with meaningful values
     // (refer to the documentation below).
@@ -460,7 +493,7 @@
     //
     // The buffer is not queued for use by the consumer.
     //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    // The slot must be in the range of [0, SLOT_COUNT).
     //
     // The buffer will not be overwritten until the fence signals.  The fence
     // will usually be the one obtained from dequeueBuffer.
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 64f191b..1002614 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -231,6 +231,8 @@
         eBufferReleaseChannelChanged = 0x40000'00000000,
         ePictureProfileHandleChanged = 0x80000'00000000,
         eAppContentPriorityChanged = 0x100000'00000000,
+        eClientDrawnCornerRadiusChanged = 0x200000'00000000,
+        eClientDrawnShadowsChanged = 0x400000'00000000,
     };
 
     layer_state_t();
@@ -251,9 +253,9 @@
     // Geometry updates.
     static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged |
             layer_state_t::eBufferTransformChanged | layer_state_t::eCornerRadiusChanged |
-            layer_state_t::eCropChanged | layer_state_t::eDestinationFrameChanged |
-            layer_state_t::eMatrixChanged | layer_state_t::ePositionChanged |
-            layer_state_t::eTransformToDisplayInverseChanged |
+            layer_state_t::eClientDrawnCornerRadiusChanged | 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::eEdgeExtensionChanged;
 
     // Buffer and related updates.
@@ -274,8 +276,8 @@
             layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
             layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
             layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
-            layer_state_t::eStretchChanged | layer_state_t::ePictureProfileHandleChanged |
-            layer_state_t::eAppContentPriorityChanged;
+            layer_state_t::eClientDrawnShadowsChanged | layer_state_t::eStretchChanged |
+            layer_state_t::ePictureProfileHandleChanged | layer_state_t::eAppContentPriorityChanged;
 
     // Changes which invalidates the layer's visible region in CE.
     static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
@@ -300,6 +302,11 @@
     static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
             layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged;
 
+    // Changes that force GPU composition.
+    static constexpr uint64_t COMPOSITION_EFFECTS = layer_state_t::eBackgroundBlurRadiusChanged |
+            layer_state_t::eBlurRegionsChanged | layer_state_t::eCornerRadiusChanged |
+            layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged;
+
     bool hasValidBuffer() const;
     void sanitize(int32_t permissions);
 
@@ -328,6 +335,8 @@
     uint8_t reserved;
     matrix22_t matrix;
     float cornerRadius;
+    float clientDrawnCornerRadius;
+    float clientDrawnShadowRadius;
     uint32_t backgroundBlurRadius;
 
     sp<SurfaceControl> relativeLayerSurfaceControl;
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 14a3513..755674d 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -558,7 +558,11 @@
     // slot that has not yet been used. The buffer allocated to a slot will also
     // be replaced if the requested buffer usage or geometry differs from that
     // of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<BufferSlot> mSlots;
+#else
     BufferSlot mSlots[NUM_BUFFER_SLOTS];
+#endif
 
     // mReqWidth is the buffer width that will be requested at the next dequeue
     // operation. It is initialized to 1.
@@ -732,6 +736,10 @@
     std::vector<sp<GraphicBuffer>> mRemovedBuffers;
     int mMaxBufferCount;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    bool mIsSlotExpansionAllowed;
+#endif
+
     sp<IProducerListener> mListenerProxy;
 
     // Get and flush the buffers of given slots, if the buffer in the slot
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0f66c8b..2215632 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -467,10 +467,7 @@
         std::vector<uint64_t> mMergedTransactionIds;
 
         uint64_t mId;
-
-        bool mAnimation = false;
-        bool mEarlyWakeupStart = false;
-        bool mEarlyWakeupEnd = false;
+        uint32_t mFlags = 0;
 
         // Indicates that the Transaction may contain buffers that should be cached. The reason this
         // is only a guess is that buffers can be removed before cache is called. This is only a
@@ -567,6 +564,15 @@
         Transaction& setCrop(const sp<SurfaceControl>& sc, const Rect& crop);
         Transaction& setCrop(const sp<SurfaceControl>& sc, const FloatRect& crop);
         Transaction& setCornerRadius(const sp<SurfaceControl>& sc, float cornerRadius);
+        // Sets the client drawn corner radius for the layer. If both a corner radius and a client
+        // radius are sent to SF, the client radius will be used. This indicates that the corner
+        // radius is drawn by the client and not SurfaceFlinger.
+        Transaction& setClientDrawnCornerRadius(const sp<SurfaceControl>& sc,
+                                                float clientDrawnCornerRadius);
+        // Sets the client drawn shadow radius for the layer. This indicates that the shadows
+        // are drawn by the client and not SurfaceFlinger.
+        Transaction& setClientDrawnShadowRadius(const sp<SurfaceControl>& sc,
+                                                float clientDrawnShadowRadius);
         Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc,
                                              int backgroundBlurRadius);
         Transaction& setBlurRegions(const sp<SurfaceControl>& sc,
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index eb3be55..420dc21 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -150,8 +150,6 @@
                 static_cast<uint32_t>(os::InputConfig::NOT_FOCUSABLE),
         NOT_TOUCHABLE =
                 static_cast<uint32_t>(os::InputConfig::NOT_TOUCHABLE),
-        PREVENT_SPLITTING =
-                static_cast<uint32_t>(os::InputConfig::PREVENT_SPLITTING),
         DUPLICATE_TOUCH_TO_WALLPAPER =
                 static_cast<uint32_t>(os::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER),
         IS_WALLPAPER =
@@ -220,9 +218,14 @@
     // An alpha of 1.0 means fully opaque and 0.0 means fully transparent.
     float alpha;
 
-    // Transform applied to individual windows.
+    // Transform applied to individual windows for input.
+    // Maps display coordinates to the window's input coordinate space.
     ui::Transform transform;
 
+    // Transform applied to get to the layer stack space of the cloned window for input.
+    // Maps display coordinates of the clone window to the layer stack space of the cloned window.
+    std::optional<ui::Transform> cloneLayerStackTransform;
+
     /*
      * This is filled in by the WM relative to the frame and then translated
      * to absolute coordinates by SurfaceFlinger once the frame is computed.
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index 6bf38c0..394a5cf 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -138,4 +138,7 @@
   description: "Remove BufferQueueProducer::dequeue's wait on this fence (or the fence entirely) to prevent deadlocks"
   bug: "339705065"
   is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 } # bq_gl_fence_cleanup
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index f07747f..87051a7 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -55,6 +55,7 @@
         "-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",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_UNLIMITED_SLOTS=true",
     ],
 
     srcs: [
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index 3b6a66e..6453885 100644
--- a/libs/gui/tests/BufferItemConsumer_test.cpp
+++ b/libs/gui/tests/BufferItemConsumer_test.cpp
@@ -22,8 +22,11 @@
 #include <gui/BufferItemConsumer.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
+#include <ui/BufferQueueDefs.h>
 #include <ui/GraphicBuffer.h>
 
+#include <unordered_set>
+
 namespace android {
 
 static constexpr int kWidth = 100;
@@ -57,6 +60,8 @@
     };
 
     void SetUp() override {
+        mBuffers.resize(BufferQueueDefs::NUM_BUFFER_SLOTS);
+
         mBIC = new BufferItemConsumer(kUsage, kMaxLockedBuffers, true);
         String8 name("BufferItemConsumer_Under_Test");
         mBIC->setName(name);
@@ -137,6 +142,11 @@
         ASSERT_EQ(NO_ERROR, ret);
     }
 
+    void DetachBuffer(int slot) {
+        ALOGD("detachBuffer: slot=%d", slot);
+        status_t ret = mBIC->detachBuffer(mBuffers[slot]);
+        ASSERT_EQ(NO_ERROR, ret);
+    }
 
     std::mutex mMutex;
     int mFreedBufferCount{0};
@@ -146,7 +156,7 @@
     sp<BufferFreedListener> mBFL;
     sp<IGraphicBufferProducer> mProducer;
     sp<IGraphicBufferConsumer> mConsumer;
-    sp<GraphicBuffer> mBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
+    std::vector<sp<GraphicBuffer>> mBuffers;
 };
 
 // Test that detaching buffer from consumer side triggers onBufferFreed.
@@ -239,4 +249,52 @@
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST_F(BufferItemConsumerTest, UnlimitedSlots_AcquireReleaseAll) {
+    ASSERT_EQ(OK, mProducer->extendSlotCount(256));
+    mBuffers.resize(256);
+
+    ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(100));
+
+    std::unordered_set<int> slots;
+    for (int i = 0; i < 100; i++) {
+        int slot;
+        DequeueBuffer(&slot);
+        slots.insert(slot);
+    }
+    EXPECT_EQ(100u, slots.size());
+
+    for (int dequeuedSlot : slots) {
+        QueueBuffer(dequeuedSlot);
+
+        int slot;
+        AcquireBuffer(&slot);
+        ReleaseBuffer(slot);
+    }
+}
+
+TEST_F(BufferItemConsumerTest, UnlimitedSlots_AcquireDetachAll) {
+    ASSERT_EQ(OK, mProducer->extendSlotCount(256));
+    mBuffers.resize(256);
+
+    ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(100));
+
+    std::unordered_set<int> slots;
+    for (int i = 0; i < 100; i++) {
+        int slot;
+        DequeueBuffer(&slot);
+        slots.insert(slot);
+    }
+    EXPECT_EQ(100u, slots.size());
+
+    for (int dequeuedSlot : slots) {
+        QueueBuffer(dequeuedSlot);
+
+        int slot;
+        AcquireBuffer(&slot);
+        DetachBuffer(slot);
+    }
+}
+#endif
+
 }  // namespace android
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 1606099..77b4ae8 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -20,6 +20,8 @@
 #include "Constants.h"
 #include "MockConsumer.h"
 
+#include <EGL/egl.h>
+
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/BufferQueue.h>
@@ -44,7 +46,9 @@
 #include <gtest/gtest.h>
 
 #include <future>
+#include <optional>
 #include <thread>
+#include <unordered_map>
 
 #include <com_android_graphics_libgui_flags.h>
 
@@ -1612,4 +1616,221 @@
     }
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+struct MockUnlimitedSlotConsumer : public MockConsumer {
+    virtual void onSlotCountChanged(int size) override { mSize = size; }
+
+    std::optional<int> mSize;
+};
+
+TEST_F(BufferQueueTest, UnlimitedSlots_FailsWhenNotAllowed) {
+    createBufferQueue();
+
+    sp<MockUnlimitedSlotConsumer> mc = sp<MockUnlimitedSlotConsumer>::make();
+    EXPECT_EQ(OK, mConsumer->consumerConnect(mc, false));
+
+    EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(64));
+    EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(32));
+    EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(128));
+
+    EXPECT_EQ(std::nullopt, mc->mSize);
+}
+
+TEST_F(BufferQueueTest, UnlimitedSlots_OnlyAllowedForExtensions) {
+    createBufferQueue();
+
+    sp<MockUnlimitedSlotConsumer> consumerListener = sp<MockUnlimitedSlotConsumer>::make();
+    EXPECT_EQ(OK, mConsumer->consumerConnect(consumerListener, false));
+    EXPECT_EQ(OK, mConsumer->allowUnlimitedSlots(true));
+
+    EXPECT_EQ(BAD_VALUE, mProducer->extendSlotCount(32));
+    EXPECT_EQ(OK, mProducer->extendSlotCount(64));
+    EXPECT_EQ(OK, mProducer->extendSlotCount(128));
+    EXPECT_EQ(128, *consumerListener->mSize);
+
+    EXPECT_EQ(OK, mProducer->extendSlotCount(128));
+    EXPECT_EQ(BAD_VALUE, mProducer->extendSlotCount(127));
+}
+
+class BufferQueueUnlimitedTest : public BufferQueueTest {
+protected:
+    static constexpr auto kMaxBufferCount = 128;
+    static constexpr auto kAcquirableBufferCount = 2;
+    static constexpr auto kDequeableBufferCount = kMaxBufferCount - kAcquirableBufferCount;
+
+    virtual void SetUp() override {
+        BufferQueueTest::SetUp();
+
+        createBufferQueue();
+        setUpConsumer();
+        setUpProducer();
+    }
+
+    void setUpConsumer() {
+        EXPECT_EQ(OK, mConsumer->consumerConnect(mConsumerListener, false));
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        EXPECT_EQ(OK, mConsumer->allowUnlimitedSlots(true));
+#endif
+        EXPECT_EQ(OK, mConsumer->setConsumerUsageBits(GraphicBuffer::USAGE_SW_READ_OFTEN));
+        EXPECT_EQ(OK, mConsumer->setDefaultBufferSize(10, 10));
+        EXPECT_EQ(OK, mConsumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+        EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(kAcquirableBufferCount));
+    }
+
+    void setUpProducer() {
+        EXPECT_EQ(OK, mProducer->extendSlotCount(kMaxBufferCount));
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK,
+                  mProducer->connect(mProducerListener, NATIVE_WINDOW_API_CPU,
+                                     /*producerControlledByApp*/ true, &output));
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        ASSERT_TRUE(output.isSlotExpansionAllowed);
+#endif
+        ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(kDequeableBufferCount));
+        ASSERT_EQ(OK, mProducer->allowAllocation(true));
+    }
+
+    std::unordered_map<int, sp<Fence>> dequeueAll() {
+        std::unordered_map<int, sp<Fence>> slotsToFences;
+
+        for (int i = 0; i < kDequeableBufferCount; ++i) {
+            int slot;
+            sp<Fence> fence;
+            sp<GraphicBuffer> buffer;
+
+            status_t ret =
+                    mProducer->dequeueBuffer(&slot, &fence, /*w*/ 0, /*h*/ 0, /*format*/ 0,
+                                             /*uint64_t*/ 0,
+                                             /*outBufferAge*/ nullptr, /*outTimestamps*/ nullptr);
+            if (ret & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+                EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buffer))
+                        << "Unable to request buffer for slot " << slot;
+            }
+            EXPECT_FALSE(slotsToFences.contains(slot));
+            slotsToFences.emplace(slot, fence);
+        }
+        EXPECT_EQ(kDequeableBufferCount, (int)slotsToFences.size());
+        return slotsToFences;
+    }
+
+    sp<MockUnlimitedSlotConsumer> mConsumerListener = sp<MockUnlimitedSlotConsumer>::make();
+    sp<StubProducerListener> mProducerListener = sp<StubProducerListener>::make();
+};
+
+TEST_F(BufferQueueUnlimitedTest, ExpandOverridesConsumerMaxBuffers) {
+    createBufferQueue();
+    setUpConsumer();
+    EXPECT_EQ(OK, mConsumer->setMaxBufferCount(10));
+
+    setUpProducer();
+
+    EXPECT_EQ(kDequeableBufferCount, (int)dequeueAll().size());
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanDetachAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        EXPECT_EQ(OK, mProducer->detachBuffer(slot));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanCancelAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanAcquireAndReleaseAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        IGraphicBufferProducer::QueueBufferInput input;
+        input.fence = fence;
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+        BufferItem buffer;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+        EXPECT_EQ(OK,
+                  mConsumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, EGL_NO_DISPLAY,
+                                           EGL_NO_SYNC, buffer.mFence));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanAcquireAndDetachAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        IGraphicBufferProducer::QueueBufferInput input;
+        input.fence = fence;
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+        BufferItem buffer;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+        EXPECT_EQ(OK, mConsumer->detachBuffer(buffer.mSlot));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, GetReleasedBuffersExtended) {
+    // First, acquire and release all the buffers so the consumer "knows" about
+    // them
+    auto slotsToFences = dequeueAll();
+
+    std::vector<bool> releasedSlots;
+    EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+    for (auto& [slot, _] : slotsToFences) {
+        EXPECT_TRUE(releasedSlots[slot])
+                << "Slots that haven't been acquired will show up as released.";
+    }
+    for (auto& [slot, fence] : slotsToFences) {
+        IGraphicBufferProducer::QueueBufferInput input;
+        input.fence = fence;
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+        BufferItem buffer;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+        EXPECT_EQ(OK,
+                  mConsumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, EGL_NO_DISPLAY,
+                                           EGL_NO_SYNC_KHR, buffer.mFence));
+    }
+
+    EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+    for (auto& [slot, _] : slotsToFences) {
+        EXPECT_FALSE(releasedSlots[slot])
+                << "Slots that have been acquired will show up as not released.";
+    }
+
+    // Then, alternatively cancel and detach (release) buffers. Only detached
+    // buffers should be returned by getReleasedBuffersExtended
+    slotsToFences = dequeueAll();
+    std::set<int> cancelledSlots;
+    std::set<int> detachedSlots;
+    bool cancel;
+    for (auto& [slot, fence] : slotsToFences) {
+        if (cancel) {
+            EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
+            cancelledSlots.insert(slot);
+        } else {
+            EXPECT_EQ(OK, mProducer->detachBuffer(slot));
+            detachedSlots.insert(slot);
+        }
+        cancel = !cancel;
+    }
+
+    EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+    for (int slot : detachedSlots) {
+        EXPECT_TRUE(releasedSlots[slot]) << "Slots that are detached are released.";
+    }
+    for (int slot : cancelledSlots) {
+        EXPECT_FALSE(releasedSlots[slot])
+                << "Slots that are still held in the queue are not released.";
+    }
+}
+#endif //  COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
 } // namespace android
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index f4239cb..9476930 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -803,6 +803,27 @@
         ::testing::ValuesIn(rgba8888TestSets));
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST(CpuConsumerSlotTest, UnlimitedSlots_AcquireReleaseAll) {
+    sp<CpuConsumer> cpuConsumer = sp<CpuConsumer>::make(3);
+    sp<Surface> surface = cpuConsumer->getSurface();
+    sp<SurfaceListener> listener = sp<StubSurfaceListener>::make();
 
+    ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, listener));
+    ASSERT_EQ(OK, surface->setMaxDequeuedBufferCount(256));
 
+    std::vector<Surface::BatchBuffer> buffers(256);
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+
+    for (auto& buffer : buffers) {
+        sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(buffer.buffer);
+        sp<Fence> fence = sp<Fence>::make(buffer.fenceFd);
+        EXPECT_EQ(OK, surface->queueBuffer(graphicBuffer, fence));
+
+        CpuConsumer::LockedBuffer nativeBuffer;
+        EXPECT_EQ(OK, cpuConsumer->lockNextBuffer(&nativeBuffer));
+        EXPECT_EQ(OK, cpuConsumer->unlockBuffer(nativeBuffer));
+    }
+}
+#endif
 } // namespace android
diff --git a/libs/gui/tests/FillBuffer.cpp b/libs/gui/tests/FillBuffer.cpp
index b60995a..11383d9 100644
--- a/libs/gui/tests/FillBuffer.cpp
+++ b/libs/gui/tests/FillBuffer.cpp
@@ -76,7 +76,7 @@
 }
 
 void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
-    const size_t PIXEL_SIZE = 4;
+    constexpr size_t PIXEL_SIZE = 4;
     for (int x = 0; x < w; x++) {
         for (int y = 0; y < h; y++) {
             off_t offset = (y * stride + x) * PIXEL_SIZE;
@@ -89,6 +89,21 @@
     }
 }
 
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride, uint8_t r, uint8_t g, uint8_t b,
+                     uint8_t a) {
+    constexpr size_t PIXEL_SIZE = 4;
+
+    for (int x = 0; x < w; x++) {
+        for (int y = 0; y < h; y++) {
+            off_t offset = (y * stride + x) * PIXEL_SIZE;
+            buf[offset] = r;
+            buf[offset + 1] = g;
+            buf[offset + 2] = b;
+            buf[offset + 3] = a;
+        }
+    }
+}
+
 void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) {
     android_native_buffer_t* anb;
     ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
diff --git a/libs/gui/tests/FillBuffer.h b/libs/gui/tests/FillBuffer.h
index b584179..f5d6b8b 100644
--- a/libs/gui/tests/FillBuffer.h
+++ b/libs/gui/tests/FillBuffer.h
@@ -30,6 +30,8 @@
         const android_native_rect_t& rect);
 
 void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride);
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride, uint8_t r, uint8_t g, uint8_t b,
+                     uint8_t a);
 
 // Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern
 // using the CPU.  This assumes that the ANativeWindow is already configured to
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
index 9ffe91f..c502533 100644
--- a/libs/gui/tests/FrameRateUtilsTest.cpp
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -34,7 +34,7 @@
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
     // Privileged APIs.
diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp
index 449533a..b22b853 100644
--- a/libs/gui/tests/SurfaceTextureGL_test.cpp
+++ b/libs/gui/tests/SurfaceTextureGL_test.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "SurfaceTextureGL_test"
 //#define LOG_NDEBUG 0
 
+#include <gmock/gmock.h>
+
 #include "SurfaceTextureGL.h"
 
 #include "DisconnectWaiter.h"
@@ -735,4 +737,30 @@
     ASSERT_NE(NO_ERROR, mST->updateTexImage());
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+TEST_F(SurfaceTextureGLTest, TestUnlimitedSlots) {
+    ASSERT_EQ(OK, mSTC->connect(NATIVE_WINDOW_API_CPU, sp<StubSurfaceListener>::make()));
+    ASSERT_EQ(OK, mSTC->setMaxDequeuedBufferCount(256));
+
+    std::vector<Surface::BatchBuffer> buffers(256);
+    ASSERT_EQ(OK, mSTC->dequeueBuffers(&buffers));
+    ASSERT_EQ(256u, buffers.size());
+    ASSERT_THAT(buffers, Each(Field(&Surface::BatchBuffer::buffer, ::testing::NotNull())));
+
+    for (size_t i = 0; i < buffers.size(); ++i) {
+        sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(buffers[i].buffer);
+        sp<Fence> fence = sp<Fence>::make(buffers[i].fenceFd);
+
+        void* buf;
+        ASSERT_EQ(OK, graphicBuffer->lock(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, &buf));
+        fillRGBA8Buffer((uint8_t*)buf, graphicBuffer->getWidth(), graphicBuffer->getHeight(),
+                        graphicBuffer->getStride(), i, i, i, i);
+        graphicBuffer->unlock();
+
+        ASSERT_EQ(OK, mSTC->queueBuffer(graphicBuffer, fence));
+        ASSERT_EQ(OK, mST->updateTexImage());
+        checkPixel(0, 0, i, i, i, i);
+    }
+}
+#endif
 } // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 3185778..646e30e 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-#include "gui/view/Surface.h"
-#include "Constants.h"
-#include "MockConsumer.h"
-
 #include <gtest/gtest.h>
 
 #include <SurfaceFlingerProperties.h>
@@ -36,10 +32,13 @@
 #include <gui/IConsumerListener.h>
 #include <gui/IGraphicBufferConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
+#include <gui/view/Surface.h>
+#include <nativebase/nativebase.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <sys/types.h>
@@ -47,6 +46,7 @@
 #include <ui/BufferQueueDefs.h>
 #include <ui/DisplayMode.h>
 #include <ui/GraphicBuffer.h>
+#include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <utils/Errors.h>
 #include <utils/String8.h>
@@ -55,9 +55,12 @@
 #include <cstddef>
 #include <cstdint>
 #include <future>
+#include <iterator>
 #include <limits>
 #include <thread>
 
+#include "Constants.h"
+#include "MockConsumer.h"
 #include "testserver/TestServerClient.h"
 
 namespace android {
@@ -2533,4 +2536,128 @@
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST_F(SurfaceTest, UnlimitedSlots_FailsOnIncompatibleConsumer) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(false));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_NE(OK, surface->setMaxDequeuedBufferCount(128))
+            << "We shouldn't be able to set high max buffer counts if the consumer doesn't allow "
+               "it";
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_CanDequeueAndQueueMoreThanOldMaximum) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+    EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+    EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+            << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+               "count arbitrarily";
+
+    std::vector<std::tuple<sp<GraphicBuffer>, sp<Fence>, int>> buffers;
+    for (int i = 0; i < 128; i++) {
+        sp<GraphicBuffer> buffer;
+        sp<Fence> fence;
+        ASSERT_EQ(OK, surface->dequeueBuffer(&buffer, &fence)) << "Unable to dequeue buffer #" << i;
+        buffers.push_back({buffer, fence, i});
+    }
+
+    for (auto& [buffer, fence, idx] : buffers) {
+        ASSERT_EQ(OK, surface->queueBuffer(buffer, fence)) << "Unable to queue buffer #" << idx;
+    }
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_CanDequeueAndDetachMoreThanOldMaximum) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+    EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+    EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+            << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+               "count arbitrarily";
+
+    std::vector<std::tuple<sp<GraphicBuffer>, sp<Fence>, int>> buffers;
+    for (int i = 0; i < 128; i++) {
+        sp<GraphicBuffer> buffer;
+        sp<Fence> fence;
+        ASSERT_EQ(OK, surface->dequeueBuffer(&buffer, &fence)) << "Unable to dequeue buffer #" << i;
+        buffers.push_back({buffer, fence, i});
+    }
+
+    for (auto& [buffer, _, idx] : buffers) {
+        ASSERT_EQ(OK, surface->detachBuffer(buffer)) << "Unable to detach buffer #" << idx;
+    }
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_BatchOperations) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+    EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+    EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+            << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+               "count arbitrarily";
+
+    std::vector<Surface::BatchBuffer> buffers(128);
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+    EXPECT_EQ(128u, buffers.size());
+
+    std::vector<Surface::BatchQueuedBuffer> queuedBuffers;
+    std::transform(buffers.begin(), buffers.end(), std::back_inserter(queuedBuffers),
+                   [](Surface::BatchBuffer& buffer) {
+                       Surface::BatchQueuedBuffer out;
+                       out.buffer = buffer.buffer;
+                       out.fenceFd = buffer.fenceFd;
+                       return out;
+                   });
+
+    std::vector<SurfaceQueueBufferOutput> outputs;
+    EXPECT_EQ(OK, surface->queueBuffers(queuedBuffers, &outputs));
+    EXPECT_EQ(128u, outputs.size());
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
 } // namespace android
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index ce22082..e3f9a07 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -40,7 +40,18 @@
     ASSERT_EQ(OK, i.writeToParcel(&p));
     p.setDataPosition(0);
     i2.readFromParcel(&p);
-    ASSERT_TRUE(i2.token == nullptr);
+    ASSERT_EQ(i2.token, nullptr);
+}
+
+TEST(WindowInfo, ParcellingWithoutCloneTransform) {
+    WindowInfo i, i2;
+    i.cloneLayerStackTransform.reset();
+
+    Parcel p;
+    ASSERT_EQ(OK, i.writeToParcel(&p));
+    p.setDataPosition(0);
+    i2.readFromParcel(&p);
+    ASSERT_EQ(i2.cloneLayerStackTransform, std::nullopt);
 }
 
 TEST(WindowInfo, Parcelling) {
@@ -71,6 +82,8 @@
     i.applicationInfo.token = new BBinder();
     i.applicationInfo.dispatchingTimeoutMillis = 0x12345678ABCD;
     i.focusTransferTarget = new BBinder();
+    i.cloneLayerStackTransform = ui::Transform();
+    i.cloneLayerStackTransform->set({5, -1, 100, 4, 0, 40, 0, 0, 1});
 
     Parcel p;
     i.writeToParcel(&p);
@@ -100,6 +113,7 @@
     ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle);
     ASSERT_EQ(i.applicationInfo, i2.applicationInfo);
     ASSERT_EQ(i.focusTransferTarget, i2.focusTransferTarget);
+    ASSERT_EQ(i.cloneLayerStackTransform, i2.cloneLayerStackTransform);
 }
 
 TEST(InputApplicationInfo, Parcelling) {
diff --git a/libs/input/AccelerationCurve.cpp b/libs/input/AccelerationCurve.cpp
index 0a92a71..0b47f3e 100644
--- a/libs/input/AccelerationCurve.cpp
+++ b/libs/input/AccelerationCurve.cpp
@@ -40,6 +40,18 @@
 constexpr std::array<double, 15> kSensitivityFactors = {1,  2,  4,  6,  7,  8,  9, 10,
                                                         11, 12, 13, 14, 16, 18, 20};
 
+// Calculates the base gain for a given pointer sensitivity value.
+//
+// The base gain is a scaling factor that is applied to the pointer movement.
+// Higher sensitivity values result in larger base gains, which in turn result
+// in faster pointer movements.
+//
+// The base gain is calculated using a linear mapping function that maps the
+// sensitivity range [-7, 7] to a base gain range [0.5, 2.0].
+double calculateBaseGain(int32_t sensitivity) {
+    return 0.5 + (sensitivity + 7) * (2.0 - 0.5) / (7 + 7);
+}
+
 } // namespace
 
 std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
@@ -60,4 +72,13 @@
     return output;
 }
 
+std::vector<AccelerationCurveSegment> createFlatAccelerationCurve(int32_t sensitivity) {
+    LOG_ALWAYS_FATAL_IF(sensitivity < -7 || sensitivity > 7, "Invalid pointer sensitivity value");
+    std::vector<AccelerationCurveSegment> output = {
+            AccelerationCurveSegment{std::numeric_limits<double>::infinity(),
+                                     calculateBaseGain(sensitivity),
+                                     /* reciprocal = */ 0}};
+    return output;
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 6a55726..56ccaab 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -327,8 +327,8 @@
                                                    android::base::unique_fd fd, sp<IBinder> token) {
     const int result = fcntl(fd, F_SETFL, O_NONBLOCK);
     if (result != 0) {
-        LOG_ALWAYS_FATAL("channel '%s' ~ Could not make socket non-blocking: %s", name.c_str(),
-                         strerror(errno));
+        LOG_ALWAYS_FATAL("channel '%s' ~ Could not make socket (%d) non-blocking: %s", name.c_str(),
+                         fd.get(), strerror(errno));
         return nullptr;
     }
     // using 'new' to access a non-public constructor
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 90d29dd..d2c49b1 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -615,7 +615,7 @@
         ALOGE("%s: Null parcel", __func__);
         return nullptr;
     }
-    std::string loadFileName = parcel->readCString();
+    std::string loadFileName = parcel->readString8().c_str();
     std::unique_ptr<KeyCharacterMap> map =
             std::make_unique<KeyCharacterMap>(KeyCharacterMap(loadFileName));
     map->mType = static_cast<KeyCharacterMap::KeyboardType>(parcel->readInt32());
@@ -704,7 +704,7 @@
         ALOGE("%s: Null parcel", __func__);
         return;
     }
-    parcel->writeCString(mLoadFileName.c_str());
+    parcel->writeString8(String8(mLoadFileName.c_str()));
     parcel->writeInt32(static_cast<int32_t>(mType));
     parcel->writeBool(mLayoutOverlayApplied);
 
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 31592cd..6ce3fba 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -76,6 +76,9 @@
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
+    /* The default mouse wheel acceleration value. */
+    const int DEFAULT_MOUSE_WHEEL_ACCELERATION = 4;
+
     /**
      * Use the default Velocity Tracker Strategy. Different axes may use different default
      * strategies.
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index da62e03..e5f7b56 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -57,16 +57,9 @@
     NOT_TOUCHABLE                = 1 << 3,
 
     /**
-     * Indicates that this window will not accept a touch event that is split between
-     * more than one window. When set:
-     *  - If this window receives a DOWN event with the first pointer, all successive
-     *    pointers that go down, regardless of their location on the screen, will be
-     *    directed to this window;
-     *  - If the DOWN event lands outside the touchable bounds of this window, no
-     *    successive pointers that go down, regardless of their location on the screen,
-     *    will be directed to this window.
+     * This flag is now deprecated and should not be used.
      */
-    PREVENT_SPLITTING            = 1 << 4,
+    DEPRECATED_PREVENT_SPLITTING = 1 << 4,
 
     /**
      * Indicates that this window shows the wallpaper behind it, so all touch events
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index fd77048..bf928f4 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -37,9 +37,9 @@
 }
 
 flag {
-  name: "split_all_touches"
+  name: "deprecate_split_touch_apis"
   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"
+  description: "Deprecate all public APIs related to split touch because now all windows behave as if split touch is permanently enabled and there's no way for a window to disable split touch."
   bug: "239934827"
 }
 
@@ -109,13 +109,6 @@
 }
 
 flag {
-  name: "enable_touchpad_fling_stop"
-  namespace: "input"
-  description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
-  bug: "281106755"
-}
-
-flag {
   name: "enable_prediction_pruning_via_jerk_thresholding"
   namespace: "input"
   description: "Enable prediction pruning based on jerk thresholds."
@@ -195,6 +188,16 @@
 }
 
 flag {
+  name: "disable_touch_input_mapper_pointer_usage"
+  namespace: "input"
+  description: "Disable the PointerUsage concept in TouchInputMapper since the old touchpad stack is no longer used."
+  bug: "281840344"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+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."
@@ -231,3 +234,13 @@
   description: "Allow cursor to transition across multiple connected displays"
   bug: "362719483"
 }
+
+flag {
+  name: "use_cloned_screen_coordinates_as_raw"
+  namespace: "input"
+  description: "Use the cloned window's layer stack (screen) space as the raw coordinate space for input going to clones"
+  bug: "377846505"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 0167c43..85a37fe 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -58,11 +58,19 @@
         "-Wno-unused-parameter",
     ],
     sanitize: {
+        address: true,
         hwaddress: true,
         undefined: true,
         all_undefined: true,
         diag: {
+            cfi: true,
+            integer_overflow: true,
+            memtag_heap: true,
             undefined: true,
+            misc_undefined: [
+                "bounds",
+                "all",
+            ],
         },
     },
     shared_libs: [
@@ -92,11 +100,6 @@
                 "libstatssocket_lazy",
             ],
         },
-        host: {
-            sanitize: {
-                address: true,
-            },
-        },
     },
     native_coverage: false,
 }
diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp
index e2eb080..5bb1d56 100644
--- a/libs/input/tests/InputVerifier_test.cpp
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
+#include <android/input.h>
+#include <android-base/result.h>
 #include <gtest/gtest.h>
+#include <input/Input.h>
 #include <input/InputVerifier.h>
 #include <string>
+#include <vector>
 
 namespace android {
 
@@ -48,7 +52,7 @@
                                      AMOTION_EVENT_ACTION_DOWN,
                                      /*pointerCount=*/properties.size(), properties.data(),
                                      coords.data(), /*flags=*/0);
-    ASSERT_TRUE(result.ok());
+    ASSERT_RESULT_OK(result);
 }
 
 } // namespace android
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index ed3e8c1..10abb7c 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -258,11 +258,11 @@
     ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1,
 
     /**
-     * The window requests a frame rate that is greater than or equal to the specified frame rate.
+     * The window requests a frame rate that is at least the specified frame rate.
      * This value should be used for UIs, animations, scrolling, and anything that is not a game
      * or video.
      */
-    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE = 2
+    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST = 2
 };
 
 /**
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index 243532b..7e179d6 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -148,7 +148,10 @@
         OP_BLUETOOTH_ADVERTISE = 114,
         OP_RECORD_INCOMING_PHONE_AUDIO = 115,
         OP_NEARBY_WIFI_DEVICES = 116,
-        _NUM_OP = 117
+        // 116 - 154 omitted due to lack of use in native
+        OP_CONTROL_AUDIO = 154,
+        OP_CONTROL_AUDIO_PARTIAL = 155,
+        _NUM_OP = 156,
     };
 
     enum {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 7e8ccef..14d08ee 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -544,9 +544,18 @@
     }
 
     if (graphicBuffer && parameters.layer.luts) {
+        const bool dimInLinearSpace = parameters.display.dimmingStage !=
+                aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
+        const ui::Dataspace runtimeEffectDataspace = !dimInLinearSpace
+                ? static_cast<ui::Dataspace>(
+                          (parameters.outputDataSpace & ui::Dataspace::STANDARD_MASK) |
+                          ui::Dataspace::TRANSFER_GAMMA2_2 |
+                          (parameters.outputDataSpace & ui::Dataspace::RANGE_MASK))
+                : parameters.outputDataSpace;
+
         shader = mLutShader.lutShader(shader, parameters.layer.luts,
                                       parameters.layer.sourceDataspace,
-                                      toSkColorSpace(parameters.outputDataSpace));
+                                      toSkColorSpace(runtimeEffectDataspace));
     }
 
     if (parameters.requiresLinearEffect) {
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 37b69f6..7331bbc 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -204,10 +204,10 @@
         BAIL("[%s] null", #expr); \
     }
 
-#define VK_CHECK(expr)                              \
-    if ((expr) != VK_SUCCESS) {                     \
-        BAIL("[%s] failed. err = %d", #expr, expr); \
-        return;                                     \
+#define VK_CHECK(expr)                                    \
+    if (VkResult result = (expr); result != VK_SUCCESS) { \
+        BAIL("[%s] failed. err = %d", #expr, result);     \
+        return;                                           \
     }
 
 #define VK_GET_PROC(F)                                                           \
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
index 69f5832..7a72d09 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
@@ -110,8 +110,40 @@
     return mContext->isDeviceLost();
 }
 
+void GraphiteGpuContext::setResourceCacheLimit(size_t maxResourceBytes) {
+    // Graphite has a separate budget for its Context and its Recorder. For now the majority of
+    // memory that Graphite will allocate will be on the Recorder and minimal amount on the Context.
+    // The main allocations on the Context are MSAA buffers (not often, if ever used in
+    // RenderEngine) and stencil buffers. However, both of these should be "memoryless" in Vulkan on
+    // tiled GPUs, so they don't actually use GPU memory. However, in Vulkan there are scenarios
+    // where Vulkan could end up using real memory for them. Skia will regularly query the device to
+    // get the real memory usage and update the budgeted appropriately. Though for all real usage
+    // patterns we don't expect to ever trigger the device to allocate real memory.
+    //
+    // Therefore, we set the full maxResourceBytes budget on the Recorder. However, in the rare
+    // chance that the devcies does allocate real memory we don't want to immediately kill device
+    // performance by constantly trashing allocations on the Context. Thus we set the Context's
+    // budget to be 50% of the total budget to make sure we allow the MSAA or Stencil buffers to be
+    // allocated in Skia and not immediately discarded. But even with this extra 50% budget, as
+    // described above, this shouldn't result in actual GPU memory usage.
+    //
+    // TODO: We will need to revise this strategy for GLES which does not have the same memoryless
+    // textures.
+    // TODO: Work in Graphite has started to move a lot more of its scratch resources to be owned
+    // by the Context and not on Recorders. This will mean most memory is actually owned by the
+    // Context and thus the budgeting here will need to be updated.
+    mContext->setMaxBudgetedBytes(maxResourceBytes / 2);
+    mRecorder->setMaxBudgetedBytes(maxResourceBytes);
+}
+
+void GraphiteGpuContext::purgeUnlockedScratchResources() {
+    mContext->freeGpuResources();
+    mRecorder->freeGpuResources();
+}
+
 void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
     mContext->dumpMemoryStatistics(traceMemoryDump);
+    mRecorder->dumpMemoryStatistics(traceMemoryDump);
 }
 
 } // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h
index 413817f..57da796 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.h
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.h
@@ -39,16 +39,10 @@
     size_t getMaxRenderTargetSize() const override;
     size_t getMaxTextureSize() const override;
     bool isAbandonedOrDeviceLost() override;
-    // No-op (large resources like textures, surfaces, images, etc. created by clients don't count
-    // towards Graphite's internal caching budgets, so adjusting its limits based on display change
-    // events should be unnecessary. Additionally, Graphite doesn't expose many cache tweaking
-    // functions yet, as its design may evolve.)
-    void setResourceCacheLimit(size_t maxResourceBytes) override{};
 
-    // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching
-    // contexts.
-    // No-op (unnecessary during context switch for Graphite's client-budgeted memory model).
-    void purgeUnlockedScratchResources() override{};
+    void setResourceCacheLimit(size_t maxResourceBytes) override;
+    void purgeUnlockedScratchResources() override;
+
     // No-op (only applicable to GL).
     void resetContextIfApplicable() override{};
 
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index da47aae..ef57c30 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -74,13 +74,6 @@
     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 {
@@ -124,11 +117,20 @@
     const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f);
     const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));
 
+    auto makeSurface = [&](float scale) -> sk_sp<SkSurface> {
+        const auto newW = ceil(static_cast<float>(blurRect.width() / scale));
+        const auto newH = ceil(static_cast<float>(blurRect.height() / scale));
+        sk_sp<SkSurface> surface =
+                context->createRenderTarget(input->imageInfo().makeWH(newW, newH));
+        LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
+        return surface;
+    };
+
     // Render into surfaces downscaled by 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, 2 * kInverseInputScale) : nullptr,
-             filterPasses >= 2 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
+            {filterPasses >= 0 ? makeSurface(1 * kInverseInputScale) : nullptr,
+             filterPasses >= 1 ? makeSurface(2 * kInverseInputScale) : nullptr,
+             filterPasses >= 2 ? makeSurface(4 * kInverseInputScale) : nullptr};
 
     // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 250.
     static const float kWeights[5] = {
diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
index 8bd0d0f..1ef83a4 100644
--- a/libs/tracing_perfetto/Android.bp
+++ b/libs/tracing_perfetto/Android.bp
@@ -21,7 +21,7 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libtracing_perfetto",
     export_include_dirs: [
         "include",
@@ -37,6 +37,7 @@
     srcs: [
         "tracing_perfetto.cpp",
         "tracing_perfetto_internal.cpp",
+        "tracing_sdk.cpp",
     ],
 
     shared_libs: [
@@ -45,6 +46,10 @@
         "libperfetto_c",
     ],
 
+    export_shared_lib_headers: [
+        "libperfetto_c",
+    ],
+
     host_supported: true,
     // for vndbinder
     vendor_available: true,
diff --git a/libs/tracing_perfetto/include/tracing_sdk.h b/libs/tracing_perfetto/include/tracing_sdk.h
new file mode 100644
index 0000000..4a6e849
--- /dev/null
+++ b/libs/tracing_perfetto/include/tracing_sdk.h
@@ -0,0 +1,461 @@
+/*
+ * 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/logging.h>
+#include <stdint.h>
+
+#include <optional>
+#include <vector>
+
+#include "perfetto/public/producer.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+
+/**
+ * The objects declared here are intended to be managed by Java.
+ * This means the Java Garbage Collector is responsible for freeing the
+ * underlying native resources.
+ *
+ * The static methods prefixed with `delete_` are special. They are designed to be
+ * invoked by Java through the `NativeAllocationRegistry` when the
+ * corresponding Java object becomes unreachable.  These methods act as
+ * callbacks to ensure proper deallocation of native resources.
+ */
+namespace tracing_perfetto {
+/**
+ * @brief Represents extra data associated with a trace event.
+ * This class manages a collection of PerfettoTeHlExtra pointers.
+ */
+class Extra;
+
+/**
+ * @brief Emits a trace event.
+ * @param type The type of the event.
+ * @param cat The category of the event.
+ * @param name The name of the event.
+ * @param arg_ptr Pointer to Extra data.
+ */
+void trace_event(int type, const PerfettoTeCategory* cat, const char* name,
+                 Extra* extra);
+
+/**
+ * @brief Gets the process track UUID.
+ */
+uint64_t get_process_track_uuid();
+
+/**
+ * @brief Gets the thread track UUID for a given PID.
+ */
+uint64_t get_thread_track_uuid(pid_t tid);
+
+/**
+ * @brief Holder for all the other classes in the file.
+ */
+class Extra {
+ public:
+  Extra();
+  void push_extra(PerfettoTeHlExtra* extra);
+  void pop_extra();
+  void clear_extras();
+  static void delete_extra(Extra* extra);
+
+  PerfettoTeHlExtra* const* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Extra);
+
+  // These PerfettoTeHlExtra pointers are really pointers to all the other
+  // types of extras: Category, DebugArg, Counter etc. Those objects are
+  // individually managed by Java.
+  std::vector<PerfettoTeHlExtra*> extras_;
+};
+
+/**
+ * @brief Represents a trace event category.
+ */
+class Category {
+ public:
+  Category(const std::string& name, const std::string& tag,
+           const std::string& severity);
+
+  ~Category();
+
+  void register_category();
+
+  void unregister_category();
+
+  bool is_category_enabled();
+
+  static void delete_category(Category* category);
+
+  const PerfettoTeCategory* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Category);
+  PerfettoTeCategory category_;
+  const std::string name_;
+  const std::string tag_;
+  const std::string severity_;
+};
+
+/**
+ * @brief Represents one end of a flow between two events.
+ */
+class Flow {
+ public:
+  Flow();
+
+  void set_process_flow(uint64_t id);
+  void set_process_terminating_flow(uint64_t id);
+  static void delete_flow(Flow* flow);
+
+  const PerfettoTeHlExtraFlow* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Flow);
+  PerfettoTeHlExtraFlow flow_;
+};
+
+/**
+ * @brief Represents a named track.
+ */
+class NamedTrack {
+ public:
+  NamedTrack(uint64_t id, uint64_t parent_uuid, const std::string& name);
+
+  static void delete_track(NamedTrack* track);
+
+  const PerfettoTeHlExtraNamedTrack* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NamedTrack);
+  const std::string name_;
+  PerfettoTeHlExtraNamedTrack track_;
+};
+
+/**
+ * @brief Represents a registered track.
+ */
+class RegisteredTrack {
+ public:
+  RegisteredTrack(uint64_t id, uint64_t parent_uuid, const std::string& name,
+                  bool is_counter);
+  ~RegisteredTrack();
+
+  void register_track();
+  void unregister_track();
+  static void delete_track(RegisteredTrack* track);
+
+  const PerfettoTeHlExtraRegisteredTrack* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RegisteredTrack);
+  PerfettoTeRegisteredTrack registered_track_;
+  PerfettoTeHlExtraRegisteredTrack track_;
+  const std::string name_;
+  const uint64_t id_;
+  const uint64_t parent_uuid_;
+  const bool is_counter_;
+};
+
+/**
+ * @brief Represents a counter track event.
+ * @tparam T The data type of the counter (int64_t or double).
+ */
+template <typename T>
+class Counter {
+ public:
+  template <typename>
+  struct always_false : std::false_type {};
+
+  struct TypeMap {
+    using type = std::invoke_result_t<decltype([]() {
+      if constexpr (std::is_same_v<T, int64_t>) {
+        return std::type_identity<PerfettoTeHlExtraCounterInt64>{};
+      } else if constexpr (std::is_same_v<T, double>) {
+        return std::type_identity<PerfettoTeHlExtraCounterDouble>{};
+      } else {
+        return std::type_identity<void>{};
+      }
+    })>::type;
+
+    static constexpr int enum_value = []() {
+      if constexpr (std::is_same_v<T, int64_t>) {
+        return PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64;
+      } else if constexpr (std::is_same_v<T, double>) {
+        return PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE;
+      } else {
+        static_assert(always_false<T>::value, "Unsupported type");
+        return 0;  // Never reached, just to satisfy return type
+      }
+    }();
+  };
+
+  Counter() {
+    static_assert(!std::is_same_v<typename TypeMap::type, void>,
+                  "Unsupported type for Counter");
+
+    typename TypeMap::type counter;
+    counter.header = {TypeMap::enum_value};
+    counter_ = std::move(counter);
+  }
+
+  void set_value(T value) {
+    if constexpr (std::is_same_v<T, int64_t>) {
+      counter_.value = value;
+    } else if constexpr (std::is_same_v<T, double>) {
+      counter_.value = value;
+    }
+  }
+
+  static void delete_counter(Counter* counter) {
+    delete counter;
+  }
+
+  const TypeMap::type* get() const {
+    return &counter_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Counter);
+  TypeMap::type counter_;
+};
+
+/**
+ * @brief Represents a debug argument for a trace event.
+ * @tparam T The data type of the argument (bool, int64_t, double, const char*).
+ */
+template <typename T>
+class DebugArg {
+ public:
+  template <typename>
+  struct always_false : std::false_type {};
+
+  struct TypeMap {
+    using type = std::invoke_result_t<decltype([]() {
+      if constexpr (std::is_same_v<T, bool>) {
+        return std::type_identity<PerfettoTeHlExtraDebugArgBool>{};
+      } else if constexpr (std::is_same_v<T, int64_t>) {
+        return std::type_identity<PerfettoTeHlExtraDebugArgInt64>{};
+      } else if constexpr (std::is_same_v<T, double>) {
+        return std::type_identity<PerfettoTeHlExtraDebugArgDouble>{};
+      } else if constexpr (std::is_same_v<T, const char*>) {
+        return std::type_identity<PerfettoTeHlExtraDebugArgString>{};
+      } else {
+        return std::type_identity<void>{};
+      }
+    })>::type;
+
+    static constexpr int enum_value = []() {
+      if constexpr (std::is_same_v<T, bool>) {
+        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL;
+      } else if constexpr (std::is_same_v<T, int64_t>) {
+        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64;
+      } else if constexpr (std::is_same_v<T, double>) {
+        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE;
+      } else if constexpr (std::is_same_v<T, const char*>) {
+        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING;
+      } else {
+        static_assert(always_false<T>::value, "Unsupported type");
+        return 0;  // Never reached, just to satisfy return type
+      }
+    }();
+  };
+
+  DebugArg(const std::string& name) : name_(name) {
+    static_assert(!std::is_same_v<typename TypeMap::type, void>,
+                  "Unsupported type for DebugArg");
+
+    typename TypeMap::type arg;
+    arg.header = {TypeMap::enum_value};
+    arg.name = name_.c_str();
+    arg_ = std::move(arg);
+  }
+
+  ~DebugArg() {
+    free_string_value();
+  }
+
+  void set_value(T value) {
+    if constexpr (std::is_same_v<T, const char*>) {
+      free_string_value();
+      arg_.value = value;
+    } else if constexpr (std::is_same_v<T, int64_t>) {
+      arg_.value = value;
+    } else if constexpr (std::is_same_v<T, bool>) {
+      arg_.value = value;
+    } else if constexpr (std::is_same_v<T, double>) {
+      arg_.value = value;
+    }
+  }
+
+  static void delete_arg(DebugArg* arg) {
+    delete arg;
+  }
+
+  const TypeMap::type* get() const {
+    return &arg_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DebugArg);
+  TypeMap::type arg_;
+  const std::string name_;
+
+  constexpr void free_string_value() {
+    if constexpr (std::is_same_v<typename TypeMap::type,
+                                 PerfettoTeHlExtraDebugArgString>) {
+      if (arg_.value) {
+        free((void*)arg_.value);
+        arg_.value = nullptr;
+      }
+    }
+  }
+};
+
+template <typename T>
+class ProtoField {
+ public:
+  template <typename>
+  struct always_false : std::false_type {};
+
+  struct TypeMap {
+    using type = std::invoke_result_t<decltype([]() {
+      if constexpr (std::is_same_v<T, int64_t>) {
+        return std::type_identity<PerfettoTeHlProtoFieldVarInt>{};
+      } else if constexpr (std::is_same_v<T, double>) {
+        return std::type_identity<PerfettoTeHlProtoFieldDouble>{};
+      } else if constexpr (std::is_same_v<T, const char*>) {
+        return std::type_identity<PerfettoTeHlProtoFieldCstr>{};
+      } else {
+        return std::type_identity<void>{};
+      }
+    })>::type;
+
+    static constexpr PerfettoTeHlProtoFieldType enum_value = []() {
+      if constexpr (std::is_same_v<T, int64_t>) {
+        return PERFETTO_TE_HL_PROTO_TYPE_VARINT;
+      } else if constexpr (std::is_same_v<T, double>) {
+        return PERFETTO_TE_HL_PROTO_TYPE_DOUBLE;
+      } else if constexpr (std::is_same_v<T, const char*>) {
+        return PERFETTO_TE_HL_PROTO_TYPE_CSTR;
+      } else {
+        static_assert(always_false<T>::value, "Unsupported type");
+        return 0;  // Never reached, just to satisfy return type
+      }
+    }();
+  };
+
+  ProtoField() {
+    static_assert(!std::is_same_v<typename TypeMap::type, void>,
+                  "Unsupported type for ProtoField");
+
+    typename TypeMap::type arg;
+    arg.header.type = TypeMap::enum_value;
+    arg_ = std::move(arg);
+  }
+
+  ~ProtoField() {
+    free_string_value();
+  }
+
+  void set_value(uint32_t id, T value) {
+    if constexpr (std::is_same_v<T, int64_t>) {
+      arg_.header.id = id;
+      arg_.value = value;
+    } else if constexpr (std::is_same_v<T, double>) {
+      arg_.header.id = id;
+      arg_.value = value;
+    } else if constexpr (std::is_same_v<T, const char*>) {
+      free_string_value();
+      arg_.header.id = id;
+      arg_.str = value;
+    }
+  }
+
+  static void delete_field(ProtoField* field) {
+    delete field;
+  }
+
+  const TypeMap::type* get() const {
+    return &arg_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProtoField);
+  TypeMap::type arg_;
+
+  constexpr void free_string_value() {
+    if constexpr (std::is_same_v<typename TypeMap::type,
+                                 PerfettoTeHlProtoFieldCstr>) {
+      if (arg_.str) {
+        free((void*)arg_.str);
+        arg_.str = nullptr;
+      }
+    }
+  }
+};
+
+class ProtoFieldNested {
+ public:
+  ProtoFieldNested();
+
+  void add_field(PerfettoTeHlProtoField* field);
+  void set_id(uint32_t id);
+  static void delete_field(ProtoFieldNested* field);
+
+  const PerfettoTeHlProtoFieldNested* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProtoFieldNested);
+  PerfettoTeHlProtoFieldNested field_;
+  // These PerfettoTeHlProtoField pointers are really pointers to all the other
+  // types of protos: PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldVarInt,
+  // PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldNested. Those objects are
+  // individually managed by Java.
+  std::vector<PerfettoTeHlProtoField*> fields_;
+};
+
+class Proto {
+ public:
+  Proto();
+
+  void add_field(PerfettoTeHlProtoField* field);
+  void clear_fields();
+  static void delete_proto(Proto* proto);
+
+  const PerfettoTeHlExtraProtoFields* get() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Proto);
+  PerfettoTeHlExtraProtoFields proto_;
+  // These PerfettoTeHlProtoField pointers are really pointers to all the other
+  // types of protos: PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldVarInt,
+  // PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldNested. Those objects are
+  // individually managed by Java.
+  std::vector<PerfettoTeHlProtoField*> fields_;
+};
+
+/**
+ * @brief Activates a trigger.
+ * @param name The name of the trigger.
+ * @param ttl_ms The time-to-live of the trigger in milliseconds.
+ */
+void activate_trigger(const char* name, uint32_t ttl_ms);
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
index d203467..0dab517 100644
--- a/libs/tracing_perfetto/tests/Android.bp
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -21,12 +21,44 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+cc_library_static {
+    name: "libtracing_perfetto_test_utils",
+    export_include_dirs: [
+        "include",
+    ],
+    static_libs: [
+        "libflagtest",
+        "libgmock",
+        "perfetto_trace_protos",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-enum-compare",
+        "-Wno-unused-function",
+    ],
+
+    srcs: [
+        "utils.cpp",
+    ],
+
+    shared_libs: [
+        "libperfetto_c",
+        "liblog",
+        "libprotobuf-cpp-lite",
+    ],
+    export_shared_lib_headers: [
+        "libperfetto_c",
+    ],
+}
+
 cc_test {
     name: "libtracing_perfetto_tests",
     static_libs: [
         "libflagtest",
         "libgmock",
         "perfetto_trace_protos",
+        "libtracing_perfetto_test_utils",
     ],
     cflags: [
         "-Wall",
@@ -42,7 +74,6 @@
     ],
     srcs: [
         "tracing_perfetto_test.cpp",
-        "utils.cpp",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/libs/tracing_perfetto/tests/include/utils.h b/libs/tracing_perfetto/tests/include/utils.h
new file mode 100644
index 0000000..b2630e1
--- /dev/null
+++ b/libs/tracing_perfetto/tests/include/utils.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.h
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <cassert>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
+// Pretty printer for gtest
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+
+class WaitableEvent {
+ public:
+  WaitableEvent() = default;
+  void Notify() {
+    std::unique_lock<std::mutex> lock(m_);
+    notified_ = true;
+    cv_.notify_one();
+  }
+  bool WaitForNotification() {
+    std::unique_lock<std::mutex> lock(m_);
+    cv_.wait(lock, [this] { return notified_; });
+    return notified_;
+  }
+  bool IsNotified() {
+    std::unique_lock<std::mutex> lock(m_);
+    return notified_;
+  }
+
+ private:
+  std::mutex m_;
+  std::condition_variable cv_;
+  bool notified_ = false;
+};
+
+class TracingSession {
+ public:
+  class Builder {
+   public:
+    Builder() = default;
+    Builder& add_enabled_category(std::string category) {
+      enabled_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_disabled_category(std::string category) {
+      disabled_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_atrace_category(std::string category) {
+      atrace_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_atrace_category_prefer_sdk(std::string category) {
+      atrace_categories_prefer_sdk_.push_back(std::move(category));
+      return *this;
+    }
+    TracingSession Build();
+
+   private:
+    std::vector<std::string> enabled_categories_;
+    std::vector<std::string> disabled_categories_;
+    std::vector<std::string> atrace_categories_;
+    std::vector<std::string> atrace_categories_prefer_sdk_;
+  };
+
+  static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
+  static TracingSession FromBytes(void *buf, size_t len);
+
+  TracingSession(TracingSession&&) noexcept;
+
+  ~TracingSession();
+
+  struct PerfettoTracingSessionImpl* session() const {
+    return session_;
+  }
+
+  bool FlushBlocking(uint32_t timeout_ms);
+  void WaitForStopped();
+  void StopBlocking();
+  std::vector<uint8_t> ReadBlocking();
+
+ private:
+  TracingSession() = default;
+  struct PerfettoTracingSessionImpl* session_;
+  std::unique_ptr<WaitableEvent> stopped_;
+};
+
+}  // namespace test_utils
+}  // namespace shlib
+}  // namespace perfetto
+
+#endif  // UTILS_H
diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
index e9fee2e..b21a090 100644
--- a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
+++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
@@ -22,6 +22,7 @@
 #include <unistd.h>
 
 #include "gtest/gtest.h"
+
 #include "perfetto/public/abi/data_source_abi.h"
 #include "perfetto/public/abi/heap_buffer.h"
 #include "perfetto/public/abi/pb_decoder_abi.h"
diff --git a/libs/tracing_perfetto/tests/utils.cpp b/libs/tracing_perfetto/tests/utils.cpp
index 8c4d4a8..af61bc2 100644
--- a/libs/tracing_perfetto/tests/utils.cpp
+++ b/libs/tracing_perfetto/tests/utils.cpp
@@ -34,36 +34,17 @@
 namespace perfetto {
 namespace shlib {
 namespace test_utils {
-namespace {
-
-std::string ToHexChars(uint8_t val) {
-  std::string ret;
-  uint8_t high_nibble = (val & 0xF0) >> 4;
-  uint8_t low_nibble = (val & 0xF);
-  static const char hex_chars[] = "0123456789ABCDEF";
-  ret.push_back(hex_chars[high_nibble]);
-  ret.push_back(hex_chars[low_nibble]);
-  return ret;
-}
-
-}  // namespace
-
 TracingSession TracingSession::Builder::Build() {
   perfetto::protos::TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(1024);
 
-  auto* track_event_ds_config = trace_config.add_data_sources()->mutable_config();
-  auto* ftrace_ds_config = trace_config.add_data_sources()->mutable_config();
-
-  track_event_ds_config->set_name("track_event");
-  track_event_ds_config->set_target_buffer(0);
-
-  ftrace_ds_config->set_name("linux.ftrace");
-  ftrace_ds_config->set_target_buffer(0);
-
   {
-    auto* ftrace_config = ftrace_ds_config->mutable_ftrace_config();
     if (!atrace_categories_.empty()) {
+      auto* ftrace_ds_config = trace_config.add_data_sources()->mutable_config();
+      ftrace_ds_config->set_name("linux.ftrace");
+      ftrace_ds_config->set_target_buffer(0);
+
+      auto* ftrace_config = ftrace_ds_config->mutable_ftrace_config();
       ftrace_config->add_ftrace_events("ftrace/print");
       for (const std::string& cat : atrace_categories_) {
         ftrace_config->add_atrace_categories(cat);
@@ -76,8 +57,14 @@
   }
 
   {
-    auto* track_event_config = track_event_ds_config->mutable_track_event_config();
     if (!enabled_categories_.empty() || !disabled_categories_.empty()) {
+      auto* track_event_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);
+
+      auto* track_event_config = track_event_ds_config->mutable_track_event_config();
+
       for (const std::string& cat : enabled_categories_) {
         track_event_config->add_enabled_categories(cat);
       }
@@ -88,13 +75,17 @@
     }
   }
 
-  struct PerfettoTracingSessionImpl* ts =
-      PerfettoTracingSessionCreate(PERFETTO_BACKEND_SYSTEM);
-
   std::string trace_config_string;
   trace_config.SerializeToString(&trace_config_string);
 
-  PerfettoTracingSessionSetup(ts, trace_config_string.data(), trace_config_string.length());
+  return TracingSession::FromBytes(trace_config_string.data(), trace_config_string.length());
+}
+
+TracingSession TracingSession::FromBytes(void *buf, size_t len) {
+  struct PerfettoTracingSessionImpl* ts =
+      PerfettoTracingSessionCreate(PERFETTO_BACKEND_SYSTEM);
+
+  PerfettoTracingSessionSetup(ts, buf, len);
 
   // Fails to start here
   PerfettoTracingSessionStartBlocking(ts);
@@ -177,39 +168,3 @@
 }  // namespace test_utils
 }  // namespace shlib
 }  // namespace perfetto
-
-void PrintTo(const PerfettoPbDecoderField& field, std::ostream* pos) {
-  std::ostream& os = *pos;
-  PerfettoPbDecoderStatus status =
-      static_cast<PerfettoPbDecoderStatus>(field.status);
-  switch (status) {
-    case PERFETTO_PB_DECODER_ERROR:
-      os << "MALFORMED PROTOBUF";
-      break;
-    case PERFETTO_PB_DECODER_DONE:
-      os << "DECODER DONE";
-      break;
-    case PERFETTO_PB_DECODER_OK:
-      switch (field.wire_type) {
-        case PERFETTO_PB_WIRE_TYPE_DELIMITED:
-          os << "\"";
-          for (size_t i = 0; i < field.value.delimited.len; i++) {
-            os << perfetto::shlib::test_utils::ToHexChars(
-                      field.value.delimited.start[i])
-               << " ";
-          }
-          os << "\"";
-          break;
-        case PERFETTO_PB_WIRE_TYPE_VARINT:
-          os << "varint: " << field.value.integer64;
-          break;
-        case PERFETTO_PB_WIRE_TYPE_FIXED32:
-          os << "fixed32: " << field.value.integer32;
-          break;
-        case PERFETTO_PB_WIRE_TYPE_FIXED64:
-          os << "fixed64: " << field.value.integer64;
-          break;
-      }
-      break;
-  }
-}
diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h
deleted file mode 100644
index 8edb414..0000000
--- a/libs/tracing_perfetto/tests/utils.h
+++ /dev/null
@@ -1,457 +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.
- */
-
-// Copied from //external/perfetto/src/shared_lib/test/utils.h
-
-#ifndef UTILS_H
-#define UTILS_H
-
-#include <cassert>
-#include <condition_variable>
-#include <cstdint>
-#include <functional>
-#include <iterator>
-#include <memory>
-#include <mutex>
-#include <ostream>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock-matchers.h"
-#include "gmock/gmock-more-matchers.h"
-#include "gtest/gtest-matchers.h"
-#include "gtest/gtest.h"
-#include "perfetto/public/abi/pb_decoder_abi.h"
-#include "perfetto/public/pb_utils.h"
-#include "perfetto/public/tracing_session.h"
-
-// Pretty printer for gtest
-void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
-
-namespace perfetto {
-namespace shlib {
-namespace test_utils {
-
-class WaitableEvent {
- public:
-  WaitableEvent() = default;
-  void Notify() {
-    std::unique_lock<std::mutex> lock(m_);
-    notified_ = true;
-    cv_.notify_one();
-  }
-  bool WaitForNotification() {
-    std::unique_lock<std::mutex> lock(m_);
-    cv_.wait(lock, [this] { return notified_; });
-    return notified_;
-  }
-  bool IsNotified() {
-    std::unique_lock<std::mutex> lock(m_);
-    return notified_;
-  }
-
- private:
-  std::mutex m_;
-  std::condition_variable cv_;
-  bool notified_ = false;
-};
-
-class TracingSession {
- public:
-  class Builder {
-   public:
-    Builder() = default;
-    Builder& add_enabled_category(std::string category) {
-      enabled_categories_.push_back(std::move(category));
-      return *this;
-    }
-    Builder& add_disabled_category(std::string category) {
-      disabled_categories_.push_back(std::move(category));
-      return *this;
-    }
-    Builder& add_atrace_category(std::string category) {
-      atrace_categories_.push_back(std::move(category));
-      return *this;
-    }
-    Builder& add_atrace_category_prefer_sdk(std::string category) {
-      atrace_categories_prefer_sdk_.push_back(std::move(category));
-      return *this;
-    }
-    TracingSession Build();
-
-   private:
-    std::vector<std::string> enabled_categories_;
-    std::vector<std::string> disabled_categories_;
-    std::vector<std::string> atrace_categories_;
-    std::vector<std::string> atrace_categories_prefer_sdk_;
-  };
-
-  static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
-
-  TracingSession(TracingSession&&) noexcept;
-
-  ~TracingSession();
-
-  struct PerfettoTracingSessionImpl* session() const {
-    return session_;
-  }
-
-  bool FlushBlocking(uint32_t timeout_ms);
-  void WaitForStopped();
-  void StopBlocking();
-  std::vector<uint8_t> ReadBlocking();
-
- private:
-  TracingSession() = default;
-  struct PerfettoTracingSessionImpl* session_;
-  std::unique_ptr<WaitableEvent> stopped_;
-};
-
-template <typename FieldSkipper>
-class FieldViewBase {
- public:
-  class Iterator {
-   public:
-    using iterator_category = std::input_iterator_tag;
-    using value_type = const PerfettoPbDecoderField;
-    using pointer = value_type;
-    using reference = value_type;
-    reference operator*() const {
-      struct PerfettoPbDecoder decoder;
-      decoder.read_ptr = read_ptr_;
-      decoder.end_ptr = end_ptr_;
-      struct PerfettoPbDecoderField field;
-      do {
-        field = PerfettoPbDecoderParseField(&decoder);
-      } while (field.status == PERFETTO_PB_DECODER_OK &&
-               skipper_.ShouldSkip(field));
-      return field;
-    }
-    Iterator& operator++() {
-      struct PerfettoPbDecoder decoder;
-      decoder.read_ptr = read_ptr_;
-      decoder.end_ptr = end_ptr_;
-      PerfettoPbDecoderSkipField(&decoder);
-      read_ptr_ = decoder.read_ptr;
-      AdvanceToFirstInterestingField();
-      return *this;
-    }
-    Iterator operator++(int) {
-      Iterator tmp = *this;
-      ++(*this);
-      return tmp;
-    }
-
-    friend bool operator==(const Iterator& a, const Iterator& b) {
-      return a.read_ptr_ == b.read_ptr_;
-    }
-    friend bool operator!=(const Iterator& a, const Iterator& b) {
-      return a.read_ptr_ != b.read_ptr_;
-    }
-
-   private:
-    Iterator(const uint8_t* read_ptr, const uint8_t* end_ptr,
-             const FieldSkipper& skipper)
-        : read_ptr_(read_ptr), end_ptr_(end_ptr), skipper_(skipper) {
-      AdvanceToFirstInterestingField();
-    }
-    void AdvanceToFirstInterestingField() {
-      struct PerfettoPbDecoder decoder;
-      decoder.read_ptr = read_ptr_;
-      decoder.end_ptr = end_ptr_;
-      struct PerfettoPbDecoderField field;
-      const uint8_t* prev_read_ptr;
-      do {
-        prev_read_ptr = decoder.read_ptr;
-        field = PerfettoPbDecoderParseField(&decoder);
-      } while (field.status == PERFETTO_PB_DECODER_OK &&
-               skipper_.ShouldSkip(field));
-      if (field.status == PERFETTO_PB_DECODER_OK) {
-        read_ptr_ = prev_read_ptr;
-      } else {
-        read_ptr_ = decoder.read_ptr;
-      }
-    }
-    friend class FieldViewBase<FieldSkipper>;
-    const uint8_t* read_ptr_;
-    const uint8_t* end_ptr_;
-    const FieldSkipper& skipper_;
-  };
-  using value_type = const PerfettoPbDecoderField;
-  using const_iterator = Iterator;
-  template <typename... Args>
-  explicit FieldViewBase(const uint8_t* begin, const uint8_t* end, Args... args)
-      : begin_(begin), end_(end), s_(args...) {
-  }
-  template <typename... Args>
-  explicit FieldViewBase(const std::vector<uint8_t>& data, Args... args)
-      : FieldViewBase(data.data(), data.data() + data.size(), args...) {
-  }
-  template <typename... Args>
-  explicit FieldViewBase(const struct PerfettoPbDecoderField& field,
-                         Args... args)
-      : s_(args...) {
-    if (field.wire_type != PERFETTO_PB_WIRE_TYPE_DELIMITED) {
-      abort();
-    }
-    begin_ = field.value.delimited.start;
-    end_ = begin_ + field.value.delimited.len;
-  }
-  Iterator begin() const {
-    return Iterator(begin_, end_, s_);
-  }
-  Iterator end() const {
-    return Iterator(end_, end_, s_);
-  }
-  PerfettoPbDecoderField front() const {
-    return *begin();
-  }
-
-  size_t size() const {
-    size_t count = 0;
-    for (auto field : *this) {
-      (void)field;
-      count++;
-    }
-    return count;
-  }
-
-  bool ok() const {
-    for (auto field : *this) {
-      if (field.status != PERFETTO_PB_DECODER_OK) {
-        return false;
-      }
-    }
-    return true;
-  }
-
- private:
-  const uint8_t* begin_;
-  const uint8_t* end_;
-  FieldSkipper s_;
-};
-
-// Pretty printer for gtest
-template <typename FieldSkipper>
-void PrintTo(const FieldViewBase<FieldSkipper>& field_view, std::ostream* pos) {
-  std::ostream& os = *pos;
-  os << "{";
-  for (PerfettoPbDecoderField f : field_view) {
-    PrintTo(f, pos);
-    os << ", ";
-  }
-  os << "}";
-}
-
-class IdFieldSkipper {
- public:
-  explicit IdFieldSkipper(uint32_t id) : id_(id) {
-  }
-  explicit IdFieldSkipper(int32_t id) : id_(static_cast<uint32_t>(id)) {
-  }
-  bool ShouldSkip(const struct PerfettoPbDecoderField& field) const {
-    return field.id != id_;
-  }
-
- private:
-  uint32_t id_;
-};
-
-class NoFieldSkipper {
- public:
-  NoFieldSkipper() = default;
-  bool ShouldSkip(const struct PerfettoPbDecoderField&) const {
-    return false;
-  }
-};
-
-// View over all the fields of a contiguous serialized protobuf message.
-//
-// Examples:
-//
-// for (struct PerfettoPbDecoderField field : FieldView(msg_begin, msg_end)) {
-//   //...
-// }
-// FieldView fields2(/*PerfettoPbDecoderField*/ nested_field);
-// FieldView fields3(/*std::vector<uint8_t>*/ data);
-// size_t num = fields1.size(); // The number of fields.
-// bool ok = fields1.ok(); // Checks that the message is not malformed.
-using FieldView = FieldViewBase<NoFieldSkipper>;
-
-// Like `FieldView`, but only considers fields with a specific id.
-//
-// Examples:
-//
-// IdFieldView fields(msg_begin, msg_end, id)
-using IdFieldView = FieldViewBase<IdFieldSkipper>;
-
-// Matches a PerfettoPbDecoderField with the specified id. Accepts another
-// matcher to match the contents of the field.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, PbField(900, VarIntField(5)));
-template <typename M>
-auto PbField(int32_t id, M m) {
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::id, id), m);
-}
-
-// Matches a PerfettoPbDecoderField submessage field. Accepts a container
-// matcher for the subfields.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, MsgField(ElementsAre(...)));
-template <typename M>
-auto MsgField(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) { return FieldView(field); };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField length delimited field. Accepts a string
-// matcher.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, StringField("string"));
-template <typename M>
-auto StringField(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) {
-    return std::string(
-        reinterpret_cast<const char*>(field.value.delimited.start),
-        field.value.delimited.len);
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField VarInt field. Accepts an integer matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, VarIntField(1)));
-template <typename M>
-auto VarIntField(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) {
-    return field.value.integer64;
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_VARINT),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField fixed64 field. Accepts an integer matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, Fixed64Field(1)));
-template <typename M>
-auto Fixed64Field(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) {
-    return field.value.integer64;
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_FIXED64),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField fixed32 field. Accepts an integer matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, Fixed32Field(1)));
-template <typename M>
-auto Fixed32Field(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) {
-    return field.value.integer32;
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_FIXED32),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField double field. Accepts a double matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, DoubleField(1.0)));
-template <typename M>
-auto DoubleField(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) {
-    return field.value.double_val;
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_FIXED64),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField float field. Accepts a float matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, FloatField(1.0)));
-template <typename M>
-auto FloatField(M m) {
-  auto f = [](const PerfettoPbDecoderField& field) {
-    return field.value.float_val;
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_FIXED32),
-      testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField submessage field. Accepts a container
-// matcher for the subfields.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, AllFieldsWithId(900, ElementsAre(...)));
-template <typename M>
-auto AllFieldsWithId(int32_t id, M m) {
-  auto f = [id](const PerfettoPbDecoderField& field) {
-    return IdFieldView(field, id);
-  };
-  return testing::AllOf(
-      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
-      testing::Field(&PerfettoPbDecoderField::wire_type,
-                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
-      testing::ResultOf(f, m));
-}
-
-}  // namespace test_utils
-}  // namespace shlib
-}  // namespace perfetto
-
-#endif  // UTILS_H
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
index c35e078..4b70213 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"
@@ -43,8 +44,10 @@
 void traceFormatBegin(uint64_t category, const char* fmt, ...) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  const bool preferAtrace = internal::shouldPreferAtrace(perfettoTeCategory, category);
-  const bool preferPerfetto = internal::isPerfettoCategoryEnabled(perfettoTeCategory);
+  const bool preferAtrace =
+      internal::shouldPreferAtrace(perfettoTeCategory, category);
+  const bool preferPerfetto =
+      internal::isPerfettoCategoryEnabled(perfettoTeCategory);
   if (CC_LIKELY(!(preferAtrace || preferPerfetto))) {
     return;
   }
@@ -57,7 +60,6 @@
   vsnprintf(buf, BUFFER_SIZE, fmt, ap);
   va_end(ap);
 
-
   if (preferAtrace) {
     atrace_begin(category, buf);
   } else if (preferPerfetto) {
@@ -99,26 +101,28 @@
 }
 
 void traceAsyncBeginForTrack(uint64_t category, const char* name,
-                               const char* trackName, int32_t cookie) {
+                             const char* trackName, int32_t cookie) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
 
   if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_async_for_track_begin(category, trackName, name, cookie);
   } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
-    internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
+    internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name,
+                                              trackName, cookie);
   }
 }
 
 void traceAsyncEndForTrack(uint64_t category, const char* trackName,
-                             int32_t cookie) {
+                           int32_t cookie) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
 
   if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
     atrace_async_for_track_end(category, trackName, cookie);
   } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
-    internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
+    internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName,
+                                            cookie);
   }
 }
 
@@ -136,8 +140,10 @@
 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);
+  const bool preferAtrace =
+      internal::shouldPreferAtrace(perfettoTeCategory, category);
+  const bool preferPerfetto =
+      internal::isPerfettoCategoryEnabled(perfettoTeCategory);
   if (CC_LIKELY(!(preferAtrace || preferPerfetto))) {
     return;
   }
@@ -158,7 +164,7 @@
 }
 
 void traceInstantForTrack(uint64_t category, const char* trackName,
-                            const char* name) {
+                          const char* name) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
 
@@ -181,20 +187,21 @@
 }
 
 void traceCounter32(uint64_t category, const char* name, int32_t value) {
-  struct PerfettoTeCategory* perfettoTeCategory = internal::toPerfettoCategory(category);
+  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));
+                                   static_cast<int64_t>(value));
   }
 }
 
 bool isTagEnabled(uint64_t category) {
   struct PerfettoTeCategory* perfettoTeCategory =
       internal::toPerfettoCategory(category);
-  return internal::isPerfettoCategoryEnabled(perfettoTeCategory)
-      || atrace_is_tag_enabled(category);
+  return internal::isPerfettoCategoryEnabled(perfettoTeCategory) ||
+         atrace_is_tag_enabled(category);
 }
 
 }  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_sdk.cpp b/libs/tracing_perfetto/tracing_sdk.cpp
new file mode 100644
index 0000000..02e8d10
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_sdk.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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 "tracing_sdk.h"
+
+#include <android-base/logging.h>
+#include <cutils/trace.h>
+
+#include <cstdarg>
+#include <cstdlib>
+
+#include "perfetto/public/abi/producer_abi.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "tracing_perfetto.h"
+
+namespace tracing_perfetto {
+void trace_event(int type, const PerfettoTeCategory* perfettoTeCategory,
+                 const char* name, tracing_perfetto::Extra* extra) {
+  bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+      perfettoTeCategory->enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+  if (enabled) {
+    extra->push_extra(nullptr);
+    PerfettoTeHlEmitImpl(perfettoTeCategory->impl, type,
+                         type == PERFETTO_TE_TYPE_COUNTER ? nullptr : name,
+                         extra->get());
+    extra->pop_extra();
+  }
+}
+
+uint64_t get_process_track_uuid() {
+  return PerfettoTeProcessTrackUuid();
+}
+
+uint64_t get_thread_track_uuid(pid_t tid) {
+  // Cating a signed pid_t to unsigned
+  return PerfettoTeProcessTrackUuid() ^ PERFETTO_STATIC_CAST(uint64_t, tid);
+}
+
+Extra::Extra() {
+}
+
+void Extra::push_extra(PerfettoTeHlExtra* ptr) {
+  extras_.push_back(ptr);
+}
+
+void Extra::pop_extra() {
+  extras_.pop_back();
+}
+
+void Extra::clear_extras() {
+  extras_.clear();
+}
+
+void Extra::delete_extra(Extra* ptr) {
+  delete ptr;
+}
+
+PerfettoTeHlExtra* const* Extra::get() const {
+  return extras_.data();
+}
+
+Category::Category(const std::string& name, const std::string& tag,
+                   const std::string& severity)
+    : category_({.enabled = &perfetto_atomic_false}),
+      name_(name),
+      tag_(tag),
+      severity_(severity) {
+}
+
+Category::~Category() {
+  unregister_category();
+}
+
+void Category::register_category() {
+  if (category_.impl) return;
+
+  std::vector<const char*> tags;
+  if (!tag_.empty()) tags.push_back(tag_.data());
+  if (!severity_.empty()) tags.push_back(severity_.data());
+
+  category_.desc = {name_.c_str(), name_.c_str(), tags.data(), tags.size()};
+
+  PerfettoTeCategoryRegister(&category_);
+  PerfettoTePublishCategories();
+}
+
+void Category::unregister_category() {
+  if (!category_.impl) return;
+
+  PerfettoTeCategoryUnregister(&category_);
+  PerfettoTePublishCategories();
+}
+
+bool Category::is_category_enabled() {
+  return PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+      (category_).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+}
+
+const PerfettoTeCategory* Category::get() const {
+  return &category_;
+}
+
+void Category::delete_category(Category* ptr) {
+  delete ptr;
+}
+
+Flow::Flow() : flow_{} {
+}
+
+void Flow::set_process_flow(uint64_t id) {
+  flow_.header.type = PERFETTO_TE_HL_EXTRA_TYPE_FLOW;
+  PerfettoTeFlow ret = PerfettoTeProcessScopedFlow(id);
+  flow_.id = ret.id;
+}
+
+void Flow::set_process_terminating_flow(uint64_t id) {
+  flow_.header.type = PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW;
+  PerfettoTeFlow ret = PerfettoTeProcessScopedFlow(id);
+  flow_.id = ret.id;
+}
+
+const PerfettoTeHlExtraFlow* Flow::get() const {
+  return &flow_;
+}
+
+void Flow::delete_flow(Flow* ptr) {
+  delete ptr;
+}
+
+NamedTrack::NamedTrack(uint64_t id, uint64_t parent_uuid,
+                       const std::string& name)
+    : name_(name),
+      track_{{PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK},
+             name_.data(),
+             id,
+             parent_uuid} {
+}
+
+const PerfettoTeHlExtraNamedTrack* NamedTrack::get() const {
+  return &track_;
+}
+
+void NamedTrack::delete_track(NamedTrack* ptr) {
+  delete ptr;
+}
+
+RegisteredTrack::RegisteredTrack(uint64_t id, uint64_t parent_uuid,
+                                 const std::string& name, bool is_counter)
+    : registered_track_{},
+      track_{{PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK},
+             &(registered_track_.impl)},
+      name_(name),
+      id_(id),
+      parent_uuid_(parent_uuid),
+      is_counter_(is_counter) {
+  register_track();
+}
+
+RegisteredTrack::~RegisteredTrack() {
+  unregister_track();
+}
+
+void RegisteredTrack::register_track() {
+  if (registered_track_.impl.descriptor) return;
+
+  if (is_counter_) {
+    PerfettoTeCounterTrackRegister(&registered_track_, name_.data(),
+                                   parent_uuid_);
+  } else {
+    PerfettoTeNamedTrackRegister(&registered_track_, name_.data(), id_,
+                                 parent_uuid_);
+  }
+}
+
+void RegisteredTrack::unregister_track() {
+  if (!registered_track_.impl.descriptor) return;
+  PerfettoTeRegisteredTrackUnregister(&registered_track_);
+}
+
+const PerfettoTeHlExtraRegisteredTrack* RegisteredTrack::get() const {
+  return &track_;
+}
+
+void RegisteredTrack::delete_track(RegisteredTrack* ptr) {
+  delete ptr;
+}
+
+Proto::Proto() : proto_({PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS}, nullptr) {
+}
+
+void Proto::add_field(PerfettoTeHlProtoField* ptr) {
+  if (!fields_.empty()) {
+    fields_.pop_back();
+  }
+
+  fields_.push_back(ptr);
+  fields_.push_back(nullptr);
+  proto_.fields = fields_.data();
+}
+
+void Proto::clear_fields() {
+  fields_.clear();
+  proto_.fields = nullptr;
+}
+
+void Proto::delete_proto(Proto* ptr) {
+  delete ptr;
+}
+
+const PerfettoTeHlExtraProtoFields* Proto::get() const {
+  return &proto_;
+}
+
+ProtoFieldNested::ProtoFieldNested()
+    : field_({PERFETTO_TE_HL_PROTO_TYPE_NESTED}, nullptr) {
+}
+
+void ProtoFieldNested::add_field(PerfettoTeHlProtoField* ptr) {
+  if (!fields_.empty()) {
+    fields_.pop_back();
+  }
+
+  fields_.push_back(ptr);
+  fields_.push_back(nullptr);
+  field_.fields = fields_.data();
+}
+
+void ProtoFieldNested::set_id(uint32_t id) {
+  fields_.clear();
+  field_.header.id = id;
+  field_.fields = nullptr;
+}
+
+void ProtoFieldNested::delete_field(ProtoFieldNested* ptr) {
+  delete ptr;
+}
+
+const PerfettoTeHlProtoFieldNested* ProtoFieldNested::get() const {
+  return &field_;
+}
+
+void activate_trigger(const char* name, uint32_t ttl_ms) {
+  const char* names[] = {name, nullptr};
+  PerfettoProducerActivateTriggers(names, ttl_ms);
+}
+}  // namespace tracing_perfetto
diff --git a/libs/ui/include/ui/ShadowSettings.h b/libs/ui/include/ui/ShadowSettings.h
index c0b83b8..06be6db 100644
--- a/libs/ui/include/ui/ShadowSettings.h
+++ b/libs/ui/include/ui/ShadowSettings.h
@@ -46,6 +46,9 @@
     // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
     float length = 0.f;
 
+    // Length of the cast shadow that is drawn by the client.
+    float clientDrawnLength = 0.f;
+
     // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
     // Otherwise the shadow will only be drawn around the edges of the casting layer.
     bool casterIsTranslucent = false;
@@ -55,6 +58,7 @@
     return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
             lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
             lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
+            lhs.clientDrawnLength == rhs.clientDrawnLength &&
             lhs.casterIsTranslucent == rhs.casterIsTranslucent;
 }
 
diff --git a/services/audiomanager/Android.bp b/services/audiomanager/Android.bp
index d11631b..afcdf74 100644
--- a/services/audiomanager/Android.bp
+++ b/services/audiomanager/Android.bp
@@ -15,6 +15,7 @@
     ],
 
     shared_libs: [
+        "av-types-aidl-cpp",
         "libutils",
         "libbinder",
         "liblog",
diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp
index f8a38d1..99360b9 100644
--- a/services/audiomanager/IAudioManager.cpp
+++ b/services/audiomanager/IAudioManager.cpp
@@ -35,6 +35,24 @@
     {
     }
 
+    // This should never fail
+    virtual sp<media::IAudioManagerNative> getNativeInterface() {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor());
+        const status_t res = remote()->transact(GET_NATIVE_INTERFACE, data, &reply, 0);
+        LOG_ALWAYS_FATAL_IF(res != OK, "%s failed with result %d", __func__, res);
+        const int ex = reply.readExceptionCode();
+        LOG_ALWAYS_FATAL_IF(ex != binder::Status::EX_NONE, "%s failed with exception %d",
+                            __func__,
+                            ex);
+        sp<IBinder> binder;
+        const status_t err = reply.readNullableStrongBinder(&binder);
+        LOG_ALWAYS_FATAL_IF(binder == nullptr, "%s failed unexpected nullptr %d", __func__, err);
+        const auto iface = checked_interface_cast<media::IAudioManagerNative>(std::move(binder));
+        LOG_ALWAYS_FATAL_IF(iface == nullptr, "%s failed unexpected interface", __func__);
+        return iface;
+    }
+
     virtual audio_unique_id_t trackPlayer(player_type_t playerType, audio_usage_t usage,
             audio_content_type_t content, const sp<IBinder>& player, audio_session_t sessionId) {
         Parcel data, reply;
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 811692f..5bf6ebb 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -98,6 +98,22 @@
     return privacySensitiveDisplays;
 }
 
+vec2 calculatePositionOnDestinationViewport(const DisplayViewport& destinationViewport,
+                                            float pointerOffset,
+                                            DisplayTopologyPosition sourceBoundary) {
+    // destination is opposite of the source boundary
+    switch (sourceBoundary) {
+        case DisplayTopologyPosition::RIGHT:
+            return {0, pointerOffset}; // left edge
+        case DisplayTopologyPosition::TOP:
+            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
+        case DisplayTopologyPosition::LEFT:
+            return {destinationViewport.logicalRight, pointerOffset}; // right edge
+        case DisplayTopologyPosition::BOTTOM:
+            return {pointerOffset, 0}; // top edge
+    }
+}
+
 } // namespace
 
 // --- PointerChoreographer ---
@@ -327,19 +343,19 @@
     // except sometimes near the corners.
     // In these cases this behaviour is not noticeable. We also do not apply unconsumed delta on
     // the destination display for the same reason.
-    DisplayPosition sourceBoundary;
+    DisplayTopologyPosition sourceBoundary;
     float cursorOffset = 0.0f;
     if (rotatedUnconsumedDelta.x > 0) {
-        sourceBoundary = DisplayPosition::RIGHT;
+        sourceBoundary = DisplayTopologyPosition::RIGHT;
         cursorOffset = rotatedCursorPosition.y;
     } else if (rotatedUnconsumedDelta.x < 0) {
-        sourceBoundary = DisplayPosition::LEFT;
+        sourceBoundary = DisplayTopologyPosition::LEFT;
         cursorOffset = rotatedCursorPosition.y;
     } else if (rotatedUnconsumedDelta.y > 0) {
-        sourceBoundary = DisplayPosition::BOTTOM;
+        sourceBoundary = DisplayTopologyPosition::BOTTOM;
         cursorOffset = rotatedCursorPosition.x;
     } else {
-        sourceBoundary = DisplayPosition::TOP;
+        sourceBoundary = DisplayTopologyPosition::TOP;
         cursorOffset = rotatedCursorPosition.x;
     }
 
@@ -369,8 +385,9 @@
     pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
     pc.setDisplayViewport(destinationViewport);
     vec2 destinationPosition =
-            calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
-                                         sourceBoundary);
+            calculatePositionOnDestinationViewport(destinationViewport,
+                                                   cursorOffset - destinationOffset,
+                                                   sourceBoundary);
 
     // Transform position back to un-rotated coordinate space before sending it to controller
     destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
@@ -379,22 +396,6 @@
     pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
 }
 
-vec2 PointerChoreographer::calculateDestinationPosition(const DisplayViewport& destinationViewport,
-                                                        float pointerOffset,
-                                                        DisplayPosition sourceBoundary) {
-    // destination is opposite of the source boundary
-    switch (sourceBoundary) {
-        case DisplayPosition::RIGHT:
-            return {0, pointerOffset}; // left edge
-        case DisplayPosition::TOP:
-            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
-        case DisplayPosition::LEFT:
-            return {destinationViewport.logicalRight, pointerOffset}; // right edge
-        case DisplayPosition::BOTTOM:
-            return {pointerOffset, 0}; // top edge
-    }
-}
-
 void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
     if (args.displayId == ui::LogicalDisplayId::INVALID) {
         return;
@@ -540,8 +541,7 @@
 }
 
 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
-    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
-        !com::android::input::flags::connected_displays_cursor()) {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
         return;
     }
     bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -607,11 +607,16 @@
     mNextListener.notify(args);
 }
 
-void PointerChoreographer::setDisplayTopology(
-        const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
-                displayTopology) {
+void PointerChoreographer::setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) {
     std::scoped_lock _l(getLock());
-    mTopology = displayTopology;
+    mTopology = displayTopologyGraph;
+
+    // make primary display default mouse display, if it was not set
+    // or the existing display was removed
+    if (mDefaultMouseDisplayId == ui::LogicalDisplayId::INVALID ||
+        mTopology.graph.find(mDefaultMouseDisplayId) != mTopology.graph.end()) {
+        mDefaultMouseDisplayId = mTopology.primaryDisplayId;
+    }
 }
 
 void PointerChoreographer::dump(std::string& dump) {
@@ -985,73 +990,17 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
-void PointerChoreographer::populateFakeDisplayTopologyLocked(
-        const std::vector<gui::DisplayInfo>& displayInfos) {
-    if (!com::android::input::flags::connected_displays_cursor()) {
-        return;
-    }
-
-    if (displayInfos.size() == mTopology.size()) {
-        bool displaysChanged = false;
-        for (const auto& displayInfo : displayInfos) {
-            if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
-                displaysChanged = true;
-                break;
-            }
-        }
-
-        if (!displaysChanged) {
-            return;
-        }
-    }
-
-    // create a fake topology assuming following order
-    // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
-    // This also adds a 100px offset on corresponding edge for better manual testing
-    //   ┌────────┐
-    //   │ next   ├─────────┐
-    // ┌─└───────┐┤ next 2  │ ...
-    // │ default │└─────────┘
-    // └─────────┘
-    mTopology.clear();
-
-    // treat default display as base, in real topology it should be the primary-display
-    ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
-    for (const auto& displayInfo : displayInfos) {
-        if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
-            continue;
-        }
-        if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
-            mTopology[previousDisplay].push_back(
-                    {displayInfo.displayId, DisplayPosition::TOP, 100});
-            mTopology[displayInfo.displayId].push_back(
-                    {previousDisplay, DisplayPosition::BOTTOM, -100});
-        } else {
-            mTopology[previousDisplay].push_back(
-                    {displayInfo.displayId, DisplayPosition::RIGHT, 100});
-            mTopology[displayInfo.displayId].push_back(
-                    {previousDisplay, DisplayPosition::LEFT, -100});
-        }
-        previousDisplay = displayInfo.displayId;
-    }
-
-    // update default pointer display. In real topology it should be the primary-display
-    if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
-        mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
-    }
-}
-
 std::optional<std::pair<const DisplayViewport*, float /*offset*/>>
 PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,
-                                                   const DisplayPosition sourceBoundary,
+                                                   const DisplayTopologyPosition sourceBoundary,
                                                    float cursorOffset) const {
-    const auto& sourceNode = mTopology.find(sourceDisplayId);
-    if (sourceNode == mTopology.end()) {
+    const auto& sourceNode = mTopology.graph.find(sourceDisplayId);
+    if (sourceNode == mTopology.graph.end()) {
         // Topology is likely out of sync with viewport info, wait for it to be updated
         LOG(WARNING) << "Source display missing from topology " << sourceDisplayId;
         return std::nullopt;
     }
-    for (const AdjacentDisplay& adjacentDisplay : sourceNode->second) {
+    for (const DisplayTopologyAdjacentDisplay& adjacentDisplay : sourceNode->second) {
         if (adjacentDisplay.position != sourceBoundary) {
             continue;
         }
@@ -1064,8 +1013,8 @@
             continue;
         }
         // target position must be within target display boundary
-        const int32_t edgeSize =
-                sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
+        const int32_t edgeSize = sourceBoundary == DisplayTopologyPosition::TOP ||
+                        sourceBoundary == DisplayTopologyPosition::BOTTOM
                 ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
                 : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
         if (cursorOffset >= adjacentDisplay.offsetPx &&
@@ -1093,7 +1042,6 @@
         mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
         mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
     }
-    mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
 }
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 939529f..c2f5ec0 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -22,6 +22,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <gui/WindowInfosListener.h>
+#include <input/DisplayTopologyGraph.h>
 #include <type_traits>
 #include <unordered_set>
 
@@ -80,6 +81,11 @@
      */
     virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
 
+    /*
+     * Used by InputManager to notify changes in the DisplayTopology
+     */
+    virtual void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) = 0;
+
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
@@ -103,6 +109,7 @@
                         ui::LogicalDisplayId displayId, DeviceId deviceId) override;
     void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
     void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
+    void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph);
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyKey(const NotifyKeyArgs& args) override;
@@ -113,24 +120,6 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
-    // TODO(b/362719483) remove these when real topology is available
-    enum class DisplayPosition {
-        RIGHT,
-        TOP,
-        LEFT,
-        BOTTOM,
-        ftl_last = BOTTOM,
-    };
-
-    struct AdjacentDisplay {
-        ui::LogicalDisplayId displayId;
-        DisplayPosition position;
-        float offsetPx;
-    };
-    void setDisplayTopology(
-            const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
-                    displayTopology);
-
     void dump(std::string& dump) override;
 
 private:
@@ -174,18 +163,16 @@
     void handleUnconsumedDeltaLocked(PointerControllerInterface& pc, const vec2& unconsumedDelta)
             REQUIRES(getLock());
 
-    void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
+            const ui::LogicalDisplayId sourceDisplayId,
+            const DisplayTopologyPosition sourceBoundary, float cursorOffset) const
             REQUIRES(getLock());
 
-    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
-            const ui::LogicalDisplayId sourceDisplayId, const DisplayPosition sourceBoundary,
-            float cursorOffset) const REQUIRES(getLock());
-
-    static vec2 calculateDestinationPosition(const DisplayViewport& destinationViewport,
-                                             float pointerOffset, DisplayPosition sourceBoundary);
-
-    std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
-            GUARDED_BY(getLock());
+    /* Topology is initialized with default-constructed value, which is an empty topology. Till we
+     * receive setDisplayTopology call.
+     * Meanwhile Choreographer will treat every display as independent disconnected display.
+     */
+    DisplayTopologyGraph mTopology GUARDED_BY(getLock());
 
     /* This listener keeps tracks of visible privacy sensitive displays and updates the
      * choreographer if there are any changes.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index cd4ed5c..4c4182d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -46,6 +46,7 @@
 #include <ctime>
 #include <queue>
 #include <sstream>
+#include <variant>
 
 #include "../InputDeviceMetricsSource.h"
 
@@ -417,7 +418,7 @@
     if (inputTarget.useDefaultPointerTransform() && !zeroCoords) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
-                                               inputTarget.displayTransform,
+                                               inputTarget.rawTransform,
                                                inputTarget.globalScaleFactor, uid, vsyncId,
                                                windowId);
     }
@@ -438,7 +439,7 @@
         transform =
                 &inputTarget.getTransformForPointer(firstMarkedBit(inputTarget.getPointerIds()));
         const ui::Transform inverseTransform = transform->inverse();
-        displayTransform = &inputTarget.displayTransform;
+        displayTransform = &inputTarget.rawTransform;
 
         // Iterate through all pointers in the event to normalize against the first.
         for (size_t i = 0; i < motionEntry.getPointerCount(); i++) {
@@ -787,38 +788,14 @@
     });
 }
 
-/**
- * In general, touch should be always split between windows. Some exceptions:
- * 1. Don't split touch if all of the below is true:
- *     (a) we have an active pointer down *and*
- *     (b) a new pointer is going down that's from the same device *and*
- *     (c) the window that's receiving the current pointer does not support split touch.
- * 2. Don't split mouse events
- */
-bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) {
-    if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
-        // We should never split mouse events
-        return false;
-    }
-    for (const TouchedWindow& touchedWindow : touchState.windows) {
-        if (touchedWindow.windowHandle->getInfo()->isSpy()) {
-            // Spy windows should not affect whether or not touch is split.
-            continue;
-        }
-        if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
-            continue;
-        }
-        if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
-                    gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
-            // Wallpaper window should not affect whether or not touch is split
-            continue;
-        }
-
-        if (touchedWindow.hasTouchingPointers(entry.deviceId)) {
-            return false;
-        }
-    }
-    return true;
+bool shouldSplitTouch(int32_t source) {
+    // We should never split mouse events. This is because the events that are produced by touchpad
+    // are sent to InputDispatcher as two fingers (for example, pinch zoom), but they need to be
+    // dispatched to the same window. In those cases, the behaviour is also slightly different from
+    // default because the events should be sent to the cursor position rather than the x,y values
+    // of each of the fingers.
+    // The "normal" (uncaptured) events produced by touchpad and by mouse have SOURCE_MOUSE.
+    return !isFromSource(source, AINPUT_SOURCE_MOUSE);
 }
 
 /**
@@ -924,6 +901,23 @@
             std::forward<InputEventInjectionResult>(e));
 }
 
+InputTarget createInputTarget(const std::shared_ptr<Connection>& connection,
+                              const sp<android::gui::WindowInfoHandle>& windowHandle,
+                              InputTarget::DispatchMode dispatchMode,
+                              ftl::Flags<InputTarget::Flags> targetFlags,
+                              const ui::Transform& rawTransform,
+                              std::optional<nsecs_t> firstDownTimeInTarget) {
+    LOG_ALWAYS_FATAL_IF(connection == nullptr);
+    InputTarget inputTarget{connection};
+    inputTarget.windowHandle = windowHandle;
+    inputTarget.dispatchMode = dispatchMode;
+    inputTarget.flags = targetFlags;
+    inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
+    inputTarget.rawTransform = rawTransform;
+    inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
+    return inputTarget;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -980,7 +974,7 @@
 
     while (!mConnectionsByToken.empty()) {
         std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
-        removeInputChannelLocked(connection->getToken(), /*notify=*/false);
+        removeInputChannelLocked(connection, /*notify=*/false);
     }
 }
 
@@ -1128,7 +1122,7 @@
     if (connection->monitor) {
         return mMonitorDispatchingTimeout;
     }
-    const sp<WindowInfoHandle> window = getWindowHandleLocked(connection->getToken());
+    const sp<WindowInfoHandle> window = mWindowInfos.findWindowHandle(connection->getToken());
     if (window != nullptr) {
         return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
     }
@@ -1333,7 +1327,7 @@
         const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
 
         sp<WindowInfoHandle> touchedWindowHandle =
-                findTouchedWindowAtLocked(displayId, x, y, isStylus);
+                mWindowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
         if (touchedWindowHandle != nullptr &&
             touchedWindowHandle->getApplicationToken() !=
                     mAwaitedFocusedApplication->getApplicationToken()) {
@@ -1346,7 +1340,8 @@
 
         // Alternatively, maybe there's a spy window that could handle this event.
         const std::vector<sp<WindowInfoHandle>> touchedSpies =
-                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus, motionEntry.deviceId);
+                mWindowInfos.findTouchedSpyWindowsAt(displayId, x, y, isStylus,
+                                                     motionEntry.deviceId, mTouchStatesByDisplay);
         for (const auto& windowHandle : touchedSpies) {
             const std::shared_ptr<Connection> connection =
                     getConnectionLocked(windowHandle->getToken());
@@ -1452,19 +1447,19 @@
     }
 }
 
-sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(ui::LogicalDisplayId displayId,
-                                                                float x, float y, bool isStylus,
-                                                                bool ignoreDragWindow) const {
+sp<WindowInfoHandle> InputDispatcher::DispatcherWindowInfo::findTouchedWindowAt(
+        ui::LogicalDisplayId displayId, float x, float y, bool isStylus,
+        const sp<android::gui::WindowInfoHandle> ignoreWindow) const {
     // Traverse windows from front to back to find touched window.
-    const auto& windowHandles = getWindowHandlesLocked(displayId);
+    const auto& windowHandles = getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
-        if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
+        if (ignoreWindow && haveSameToken(windowHandle, ignoreWindow)) {
             continue;
         }
 
         const WindowInfo& info = *windowHandle->getInfo();
         if (!info.isSpy() &&
-            windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
+            windowAcceptsTouchAt(info, displayId, x, y, isStylus, getDisplayTransform(displayId))) {
             return windowHandle;
         }
     }
@@ -1479,7 +1474,7 @@
     }
     // Traverse windows from front to back until we encounter the touched window.
     std::vector<InputTarget> outsideTargets;
-    const auto& windowHandles = getWindowHandlesLocked(displayId);
+    const auto& windowHandles = mWindowInfos.getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         if (windowHandle == touchedWindow) {
             // Stop iterating once we found a touched window. Any WATCH_OUTSIDE_TOUCH window
@@ -1499,28 +1494,18 @@
     return outsideTargets;
 }
 
-std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
-        ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId) const {
+std::vector<sp<WindowInfoHandle>> InputDispatcher::DispatcherWindowInfo::findTouchedSpyWindowsAt(
+        ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
+        const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay) const {
     // Traverse windows from front to back and gather the touched spy windows.
     std::vector<sp<WindowInfoHandle>> spyWindows;
-    const auto& windowHandles = getWindowHandlesLocked(displayId);
+    const auto& windowHandles = getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         const WindowInfo& info = *windowHandle->getInfo();
-        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
-            // 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 (!windowAcceptsTouchAt(info, displayId, x, y, isStylus,
+                                  getDisplayTransform(displayId))) {
+            // Skip if the pointer is outside of the window.
+            continue;
         }
         if (!info.isSpy()) {
             // The first touched non-spy window was found, so return the spy windows touched so far.
@@ -1828,7 +1813,7 @@
 void InputDispatcher::dispatchTouchModeChangeLocked(
         nsecs_t currentTime, const std::shared_ptr<const TouchModeEntry>& entry) {
     const std::vector<sp<WindowInfoHandle>>& windowHandles =
-            getWindowHandlesLocked(entry->displayId);
+            mWindowInfos.getWindowHandlesForDisplay(entry->displayId);
     if (windowHandles.empty()) {
         return;
     }
@@ -2216,7 +2201,7 @@
 
     sp<WindowInfoHandle> windowHandle;
     if (!connection->monitor) {
-        windowHandle = getWindowHandleLocked(connection->getToken());
+        windowHandle = mWindowInfos.findWindowHandle(connection->getToken());
         if (windowHandle == nullptr) {
             // The window that is receiving this ANR was removed, so there is no need to generate
             // cancellations, because the cancellations would have already been generated when
@@ -2427,7 +2412,7 @@
         tempTouchState = *oldState;
     }
 
-    bool isSplit = shouldSplitTouch(tempTouchState, entry);
+    const bool isSplit = shouldSplitTouch(entry.source);
 
     const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
@@ -2440,11 +2425,6 @@
     const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
             maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
             maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
-    const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
-
-    if (newGesture) {
-        isSplit = false;
-    }
 
     if (isDown && tempTouchState.hasHoveringPointers(entry.deviceId)) {
         // Compatibility behaviour: ACTION_DOWN causes HOVER_EXIT to get generated.
@@ -2472,7 +2452,7 @@
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
         sp<WindowInfoHandle> newTouchedWindowHandle =
-                findTouchedWindowAtLocked(displayId, x, y, isStylus);
+                mWindowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
 
         if (isDown) {
             targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointer.id);
@@ -2480,17 +2460,6 @@
         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 (!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) {
@@ -2498,27 +2467,9 @@
             return injectionError(InputEventInjectionResult::TARGET_MISMATCH);
         }
 
-        // Figure out whether splitting will be allowed for this window.
-        if (newTouchedWindowHandle != nullptr) {
-            if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
-                // New window supports splitting, but we should never split mouse events.
-                isSplit = !isFromMouse;
-            } else if (isSplit) {
-                // New window does not support splitting but we have already split events.
-                // Ignore the new window.
-                LOG(INFO) << "Skipping " << newTouchedWindowHandle->getName()
-                          << " because it doesn't support split touch";
-                newTouchedWindowHandle = nullptr;
-            }
-        } else {
-            // No window is touched, so set split to true. This will allow the next pointer down to
-            // be delivered to a new window which supports split touch. Pointers from a mouse device
-            // should never be split.
-            isSplit = !isFromMouse;
-        }
-
         std::vector<sp<WindowInfoHandle>> newTouchedWindows =
-                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus, entry.deviceId);
+                mWindowInfos.findTouchedSpyWindowsAt(displayId, x, y, isStylus, entry.deviceId,
+                                                     mTouchStatesByDisplay);
         if (newTouchedWindowHandle != nullptr) {
             // Process the foreground window first so that it is the first to receive the event.
             newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
@@ -2655,7 +2606,7 @@
                     tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             sp<WindowInfoHandle> newTouchedWindowHandle =
-                    findTouchedWindowAtLocked(displayId, x, y, isStylus);
+                    mWindowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
 
             // Verify targeted injection.
             if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
@@ -2689,9 +2640,6 @@
                                              targets);
 
                 // Make a slippery entrance into the new window.
-                if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
-                    isSplit = !isFromMouse;
-                }
 
                 ftl::Flags<InputTarget::Flags> targetFlags;
                 if (canReceiveForegroundTouches(*newTouchedWindowHandle->getInfo())) {
@@ -2779,7 +2727,7 @@
             for (InputTarget& target : targets) {
                 if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
                     sp<WindowInfoHandle> targetWindow =
-                            getWindowHandleLocked(target.connection->getToken());
+                            mWindowInfos.findWindowHandle(target.connection->getToken());
                     if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
                         target.flags |= InputTarget::Flags::ZERO_COORDS;
                     }
@@ -2879,7 +2827,8 @@
     constexpr bool isStylus = false;
 
     sp<WindowInfoHandle> dropWindow =
-            findTouchedWindowAtLocked(displayId, x, y, isStylus, /*ignoreDragWindow=*/true);
+            mWindowInfos.findTouchedWindowAt(displayId, x, y, isStylus, /*ignoreWindow=*/
+                                             mDragState->dragWindow);
     if (dropWindow) {
         vec2 local = dropWindow->getInfo()->transform.transform(x, y);
         sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
@@ -2934,8 +2883,8 @@
             constexpr bool isStylus = false;
 
             sp<WindowInfoHandle> hoverWindowHandle =
-                    findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
-                                              /*ignoreDragWindow=*/true);
+                    mWindowInfos.findTouchedWindowAt(entry.displayId, x, y, isStylus,
+                                                     /*ignoreWindow=*/mDragState->dragWindow);
             // enqueue drag exit if needed.
             if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
                 !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
@@ -2970,31 +2919,6 @@
     }
 }
 
-std::optional<InputTarget> InputDispatcher::createInputTargetLocked(
-        const sp<android::gui::WindowInfoHandle>& windowHandle,
-        InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
-        std::optional<nsecs_t> firstDownTimeInTarget) const {
-    std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
-    if (connection == nullptr) {
-        ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str());
-        return {};
-    }
-    InputTarget inputTarget{connection};
-    inputTarget.windowHandle = windowHandle;
-    inputTarget.dispatchMode = dispatchMode;
-    inputTarget.flags = targetFlags;
-    inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
-    inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
-    const auto& displayInfoIt = mDisplayInfos.find(windowHandle->getInfo()->displayId);
-    if (displayInfoIt != mDisplayInfos.end()) {
-        inputTarget.displayTransform = displayInfoIt->second.transform;
-    } else {
-        // DisplayInfo not found for this window on display windowHandle->getInfo()->displayId.
-        // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
-    }
-    return inputTarget;
-}
-
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             InputTarget::DispatchMode dispatchMode,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
@@ -3009,13 +2933,16 @@
     const WindowInfo* windowInfo = windowHandle->getInfo();
 
     if (it == inputTargets.end()) {
-        std::optional<InputTarget> target =
-                createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
-                                        firstDownTimeInTarget);
-        if (!target) {
+        std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
+        if (connection == nullptr) {
+            ALOGW("Not creating InputTarget for %s, no input channel",
+                  windowHandle->getName().c_str());
             return;
         }
-        inputTargets.push_back(*target);
+        inputTargets.push_back(
+                createInputTarget(connection, windowHandle, dispatchMode, targetFlags,
+                                  mWindowInfos.getRawTransform(*windowHandle->getInfo()),
+                                  firstDownTimeInTarget));
         it = inputTargets.end() - 1;
     }
 
@@ -3060,13 +2987,16 @@
     const WindowInfo* windowInfo = windowHandle->getInfo();
 
     if (it == inputTargets.end()) {
-        std::optional<InputTarget> target =
-                createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
-                                        firstDownTimeInTarget);
-        if (!target) {
+        std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
+        if (connection == nullptr) {
+            ALOGW("Not creating InputTarget for %s, no input channel",
+                  windowHandle->getName().c_str());
             return;
         }
-        inputTargets.push_back(*target);
+        inputTargets.push_back(
+                createInputTarget(connection, windowHandle, dispatchMode, targetFlags,
+                                  mWindowInfos.getRawTransform(*windowHandle->getInfo()),
+                                  firstDownTimeInTarget));
         it = inputTargets.end() - 1;
     }
 
@@ -3098,11 +3028,10 @@
     for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
         InputTarget target{monitor.connection};
         // target.firstDownTimeInTarget is not set for global monitors. It is only required in split
-        // touch and global monitoring works as intended even without setting firstDownTimeInTarget
-        if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
-            target.displayTransform = it->second.transform;
-        }
-        target.setDefaultPointerTransform(target.displayTransform);
+        // touch and global monitoring works as intended even without setting firstDownTimeInTarget.
+        // Since global monitors don't have windows, use the display transform as the raw transform.
+        target.rawTransform = mWindowInfos.getDisplayTransform(displayId);
+        target.setDefaultPointerTransform(target.rawTransform);
         inputTargets.push_back(target);
     }
 }
@@ -3164,7 +3093,8 @@
         const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
     const WindowInfo* windowInfo = windowHandle->getInfo();
     ui::LogicalDisplayId displayId = windowInfo->displayId;
-    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            mWindowInfos.getWindowHandlesForDisplay(displayId);
     TouchOcclusionInfo info;
     info.hasBlockingOcclusion = false;
     info.obscuringOpacity = 0;
@@ -3176,7 +3106,8 @@
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId)) &&
+            windowOccludesTouchAt(*otherInfo, displayId, x, y,
+                                  mWindowInfos.getDisplayTransform(displayId)) &&
             !haveSameApplicationToken(windowInfo, otherInfo)) {
             if (DEBUG_TOUCH_OCCLUSION) {
                 info.debugInfo.push_back(
@@ -3248,14 +3179,16 @@
 bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<WindowInfoHandle>& windowHandle,
                                                     float x, float y) const {
     ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
-    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            mWindowInfos.getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (windowHandle == otherHandle) {
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) {
+            windowOccludesTouchAt(*otherInfo, displayId, x, y,
+                                  mWindowInfos.getDisplayTransform(displayId))) {
             return true;
         }
     }
@@ -3264,7 +3197,8 @@
 
 bool InputDispatcher::isWindowObscuredLocked(const sp<WindowInfoHandle>& windowHandle) const {
     ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
-    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            mWindowInfos.getWindowHandlesForDisplay(displayId);
     const WindowInfo* windowInfo = windowHandle->getInfo();
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (windowHandle == otherHandle) {
@@ -3921,7 +3855,7 @@
                           "event to it, status=%s(%d)",
                           connection->getInputChannelName().c_str(), statusToString(status).c_str(),
                           status);
-                    abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
+                    abortBrokenDispatchCycleLocked(connection, /*notify=*/true);
                 } else {
                     // Pipe is full and we are waiting for the app to finish process some events
                     // before sending more events to it.
@@ -3936,7 +3870,7 @@
                       "status=%s(%d)",
                       connection->getInputChannelName().c_str(), statusToString(status).c_str(),
                       status);
-                abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
+                abortBrokenDispatchCycleLocked(connection, /*notify=*/true);
             }
             return;
         }
@@ -4011,8 +3945,7 @@
     postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
-                                                     const std::shared_ptr<Connection>& connection,
+void InputDispatcher::abortBrokenDispatchCycleLocked(const std::shared_ptr<Connection>& connection,
                                                      bool notify) {
     if (DEBUG_DISPATCH_CYCLE) {
         LOG(INFO) << "channel '" << connection->getInputChannelName() << "'~ " << __func__
@@ -4118,7 +4051,8 @@
     } else {
         // Monitor channels are never explicitly unregistered.
         // We do it automatically when the remote endpoint is closed so don't warn about them.
-        const bool stillHaveWindowHandle = getWindowHandleLocked(connection->getToken()) != nullptr;
+        const bool stillHaveWindowHandle =
+                mWindowInfos.findWindowHandle(connection->getToken()) != nullptr;
         notify = !connection->monitor && stillHaveWindowHandle;
         if (notify) {
             ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred.  events=0x%x",
@@ -4127,7 +4061,7 @@
     }
 
     // Remove the channel.
-    removeInputChannelLocked(connection->getToken(), notify);
+    removeInputChannelLocked(connection, notify);
     return 0; // remove the callback
 }
 
@@ -4153,11 +4087,11 @@
     // Follow up by generating cancellations for all windows, because we don't explicitly track
     // the windows that have an ongoing focus event stream.
     if (cancelNonPointers) {
-        for (const auto& [_, handles] : mWindowHandlesByDisplay) {
-            for (const auto& windowHandle : handles) {
-                synthesizeCancelationEventsForWindowLocked(windowHandle, options);
-            }
-        }
+        mWindowInfos.forEachWindowHandle(
+                [&](const sp<android::gui::WindowInfoHandle>& windowHandle) {
+                    base::ScopedLockAssertion assumeLocked(mLock);
+                    synthesizeCancelationEventsForWindowLocked(windowHandle, options);
+                });
     }
 
     // Cancel monitors.
@@ -4281,11 +4215,11 @@
                                                  motionEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
-                    const auto it = mDisplayInfos.find(motionEntry.displayId);
-                    if (it != mDisplayInfos.end()) {
-                        targets.back().displayTransform = it->second.transform;
-                        targets.back().setDefaultPointerTransform(it->second.transform);
-                    }
+                    // Since we don't have a window, use the display transform as the raw transform.
+                    const ui::Transform displayTransform =
+                            mWindowInfos.getDisplayTransform(motionEntry.displayId);
+                    targets.back().rawTransform = displayTransform;
+                    targets.back().setDefaultPointerTransform(displayTransform);
                 }
                 logOutboundMotionDetails("cancel - ", motionEntry);
                 break;
@@ -4340,7 +4274,7 @@
     }
 
     const auto [_, touchedWindowState, displayId] =
-            findTouchStateWindowAndDisplayLocked(connection->getToken());
+            findTouchStateWindowAndDisplay(connection->getToken(), mTouchStatesByDisplay);
     if (touchedWindowState == nullptr) {
         LOG(FATAL) << __func__ << ": Touch state is out of sync: No touched window for token";
     }
@@ -4367,11 +4301,11 @@
                                                  targets);
                 } else {
                     targets.emplace_back(connection, targetFlags);
-                    const auto it = mDisplayInfos.find(motionEntry.displayId);
-                    if (it != mDisplayInfos.end()) {
-                        targets.back().displayTransform = it->second.transform;
-                        targets.back().setDefaultPointerTransform(it->second.transform);
-                    }
+                    // Since we don't have a window, use the display transform as the raw transform.
+                    const ui::Transform displayTransform =
+                            mWindowInfos.getDisplayTransform(motionEntry.displayId);
+                    targets.back().rawTransform = displayTransform;
+                    targets.back().setDefaultPointerTransform(displayTransform);
                 }
                 logOutboundMotionDetails("down - ", motionEntry);
                 break;
@@ -4642,10 +4576,7 @@
         }
 
         if (shouldSendMotionToInputFilterLocked(args)) {
-            ui::Transform displayTransform;
-            if (const auto it = mDisplayInfos.find(args.displayId); it != mDisplayInfos.end()) {
-                displayTransform = it->second.transform;
-            }
+            ui::Transform displayTransform = mWindowInfos.getDisplayTransform(args.displayId);
 
             mLock.unlock();
 
@@ -4939,6 +4870,19 @@
                 return InputEventInjectionResult::FAILED;
             }
 
+            if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+                // Set the flag anyway if we already have an ongoing motion gesture. That
+                // would allow us to complete the processing of the current stroke.
+                const auto touchStateIt = mTouchStatesByDisplay.find(displayId);
+                if (touchStateIt != mTouchStatesByDisplay.end()) {
+                    const TouchState& touchState = touchStateIt->second;
+                    if (touchState.hasTouchingPointers(resolvedDeviceId) ||
+                        touchState.hasHoveringPointers(resolvedDeviceId)) {
+                        policyFlags |= POLICY_FLAG_PASS_TO_USER;
+                    }
+                }
+            }
+
             const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
             const size_t pointerCount = motionEvent.getPointerCount();
             const std::vector<PointerProperties>
@@ -5149,9 +5093,8 @@
         MotionEntry& entry, const ui::Transform& injectedTransform) const {
     // Input injection works in the logical display coordinate space, but the input pipeline works
     // display space, so we need to transform the injected events accordingly.
-    const auto it = mDisplayInfos.find(entry.displayId);
-    if (it == mDisplayInfos.end()) return;
-    const auto& transformToDisplay = it->second.transform.inverse() * injectedTransform;
+    const ui::Transform displayTransform = mWindowInfos.getDisplayTransform(entry.displayId);
+    const auto& transformToDisplay = displayTransform.inverse() * injectedTransform;
 
     if (entry.xCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION &&
         entry.yCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION) {
@@ -5184,14 +5127,7 @@
     }
 }
 
-const std::vector<sp<WindowInfoHandle>>& InputDispatcher::getWindowHandlesLocked(
-        ui::LogicalDisplayId displayId) const {
-    static const std::vector<sp<WindowInfoHandle>> EMPTY_WINDOW_HANDLES;
-    auto it = mWindowHandlesByDisplay.find(displayId);
-    return it != mWindowHandlesByDisplay.end() ? it->second : EMPTY_WINDOW_HANDLES;
-}
-
-sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
+sp<WindowInfoHandle> InputDispatcher::DispatcherWindowInfo::findWindowHandle(
         const sp<IBinder>& windowHandleToken, std::optional<ui::LogicalDisplayId> displayId) const {
     if (windowHandleToken == nullptr) {
         return nullptr;
@@ -5210,7 +5146,7 @@
     }
 
     // Only look through the requested display.
-    for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(*displayId)) {
+    for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesForDisplay(*displayId)) {
         if (windowHandle->getToken() == windowHandleToken) {
             return windowHandle;
         }
@@ -5218,7 +5154,7 @@
     return nullptr;
 }
 
-sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
+bool InputDispatcher::DispatcherWindowInfo::isWindowPresent(
         const sp<WindowInfoHandle>& windowHandle) const {
     for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
         for (const sp<WindowInfoHandle>& handle : windowHandles) {
@@ -5230,25 +5166,108 @@
                           windowHandle->getName().c_str(), displayId.toString().c_str(),
                           windowHandle->getInfo()->displayId.toString().c_str());
                 }
-                return handle;
+                return true;
             }
         }
     }
-    return nullptr;
+    return false;
 }
 
 sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(
         ui::LogicalDisplayId displayId) const {
     sp<IBinder> focusedToken = mFocusResolver.getFocusedWindowToken(displayId);
-    return getWindowHandleLocked(focusedToken, displayId);
+    return mWindowInfos.findWindowHandle(focusedToken, displayId);
 }
 
-ui::Transform InputDispatcher::getTransformLocked(ui::LogicalDisplayId displayId) const {
+void InputDispatcher::DispatcherWindowInfo::setWindowHandlesForDisplay(
+        ui::LogicalDisplayId displayId, std::vector<sp<WindowInfoHandle>>&& windowHandles) {
+    // Insert or replace
+    mWindowHandlesByDisplay[displayId] = std::move(windowHandles);
+}
+
+void InputDispatcher::DispatcherWindowInfo::setDisplayInfos(
+        const std::vector<android::gui::DisplayInfo>& displayInfos) {
+    mDisplayInfos.clear();
+    for (const auto& displayInfo : displayInfos) {
+        mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
+    }
+}
+
+void InputDispatcher::DispatcherWindowInfo::removeDisplay(ui::LogicalDisplayId displayId) {
+    mWindowHandlesByDisplay.erase(displayId);
+}
+
+const std::vector<sp<android::gui::WindowInfoHandle>>&
+InputDispatcher::DispatcherWindowInfo::getWindowHandlesForDisplay(
+        ui::LogicalDisplayId displayId) const {
+    static const std::vector<sp<WindowInfoHandle>> EMPTY_WINDOW_HANDLES;
+    const auto it = mWindowHandlesByDisplay.find(displayId);
+    return it != mWindowHandlesByDisplay.end() ? it->second : EMPTY_WINDOW_HANDLES;
+}
+
+void InputDispatcher::DispatcherWindowInfo::forEachWindowHandle(
+        std::function<void(const sp<android::gui::WindowInfoHandle>&)> f) const {
+    for (const auto& [_, windowHandles] : mWindowHandlesByDisplay) {
+        for (const auto& windowHandle : windowHandles) {
+            f(windowHandle);
+        }
+    }
+}
+
+void InputDispatcher::DispatcherWindowInfo::forEachDisplayId(
+        std::function<void(ui::LogicalDisplayId)> f) const {
+    for (const auto& [displayId, _] : mWindowHandlesByDisplay) {
+        f(displayId);
+    }
+}
+
+ui::Transform InputDispatcher::DispatcherWindowInfo::getDisplayTransform(
+        ui::LogicalDisplayId displayId) const {
     auto displayInfoIt = mDisplayInfos.find(displayId);
     return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform
                                                 : kIdentityTransform;
 }
 
+ui::Transform InputDispatcher::DispatcherWindowInfo::getRawTransform(
+        const android::gui::WindowInfo& windowInfo) const {
+    // If the window has a cloneLayerStackTransform, always use it as the transform for the "getRaw"
+    // APIs. If not, fall back to using the DisplayInfo transform of the window's display.
+    return (input_flags::use_cloned_screen_coordinates_as_raw() &&
+            windowInfo.cloneLayerStackTransform)
+            ? *windowInfo.cloneLayerStackTransform
+            : getDisplayTransform(windowInfo.displayId);
+}
+
+std::string InputDispatcher::DispatcherWindowInfo::dumpDisplayAndWindowInfo() const {
+    std::string dump;
+    if (!mWindowHandlesByDisplay.empty()) {
+        for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
+            dump += StringPrintf("Display: %s\n", displayId.toString().c_str());
+            if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
+                const auto& displayInfo = it->second;
+                dump += StringPrintf(INDENT "logicalSize=%dx%d\n", displayInfo.logicalWidth,
+                                     displayInfo.logicalHeight);
+                displayInfo.transform.dump(dump, "transform", INDENT3);
+            } else {
+                dump += INDENT "No DisplayInfo found!\n";
+            }
+
+            if (!windowHandles.empty()) {
+                dump += INDENT "Windows:\n";
+                for (size_t i = 0; i < windowHandles.size(); i++) {
+                    dump += StringPrintf(INDENT2 "%zu: %s", i,
+                                         streamableToString(*windowHandles[i]).c_str());
+                }
+            } else {
+                dump += INDENT "Windows: <none>\n";
+            }
+        }
+    } else {
+        dump += "Displays: <none>\n";
+    }
+    return dump;
+}
+
 bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& window,
                                                    const MotionEntry& motionEntry) const {
     const WindowInfo& info = *window->getInfo();
@@ -5316,13 +5335,14 @@
         ui::LogicalDisplayId displayId) {
     if (windowInfoHandles.empty()) {
         // Remove all handles on a display if there are no windows left.
-        mWindowHandlesByDisplay.erase(displayId);
+        mWindowInfos.removeDisplay(displayId);
         return;
     }
 
     // Since we compare the pointer of input window handles across window updates, we need
     // to make sure the handle object for the same window stays unchanged across updates.
-    const std::vector<sp<WindowInfoHandle>>& oldHandles = getWindowHandlesLocked(displayId);
+    const std::vector<sp<WindowInfoHandle>>& oldHandles =
+            mWindowInfos.getWindowHandlesForDisplay(displayId);
     std::unordered_map<int32_t /*id*/, sp<WindowInfoHandle>> oldHandlesById;
     for (const sp<WindowInfoHandle>& handle : oldHandles) {
         oldHandlesById[handle->getId()] = handle;
@@ -5361,8 +5381,7 @@
         }
     }
 
-    // Insert or replace
-    mWindowHandlesByDisplay[displayId] = newHandles;
+    mWindowInfos.setWindowHandlesForDisplay(displayId, std::move(newHandles));
 }
 
 /**
@@ -5412,12 +5431,14 @@
     }
 
     // Copy old handles for release if they are no longer present.
-    const std::vector<sp<WindowInfoHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
+    const std::vector<sp<WindowInfoHandle>> oldWindowHandles =
+            mWindowInfos.getWindowHandlesForDisplay(displayId);
     const sp<WindowInfoHandle> removedFocusedWindowHandle = getFocusedWindowHandleLocked(displayId);
 
     updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
 
-    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            mWindowInfos.getWindowHandlesForDisplay(displayId);
 
     std::optional<FocusResolver::FocusChanges> changes =
             mFocusResolver.setInputWindows(displayId, windowHandles);
@@ -5429,7 +5450,7 @@
         TouchState& state = it->second;
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
-            if (getWindowHandleLocked(touchedWindow.windowHandle) != nullptr) {
+            if (mWindowInfos.isWindowPresent(touchedWindow.windowHandle)) {
                 i++;
                 continue;
             }
@@ -5473,7 +5494,8 @@
                     [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 ui::Transform displayTransform =
+                                mWindowInfos.getDisplayTransform(displayId);
                         const bool stillAcceptsTouch =
                                 windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(),
                                                      displayId, x, y, isStylus, displayTransform);
@@ -5495,7 +5517,7 @@
     // Otherwise, they might stick around until the window handle is destroyed
     // which might not happen until the next GC.
     for (const sp<WindowInfoHandle>& oldWindowHandle : oldWindowHandles) {
-        if (getWindowHandleLocked(oldWindowHandle) == nullptr) {
+        if (!mWindowInfos.isWindowPresent(oldWindowHandle)) {
             if (DEBUG_FOCUS) {
                 ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
             }
@@ -5572,7 +5594,7 @@
                     mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
             if (oldFocusedWindowToken != nullptr) {
                 const auto windowHandle =
-                        getWindowHandleLocked(oldFocusedWindowToken, mFocusedDisplayId);
+                        mWindowInfos.findWindowHandle(oldFocusedWindowToken, mFocusedDisplayId);
                 if (windowHandle == nullptr) {
                     LOG(FATAL) << __func__ << ": Previously focused token did not have a window";
                 }
@@ -5711,7 +5733,7 @@
     if (focusedToken == nullptr) {
         return false;
     }
-    sp<WindowInfoHandle> windowHandle = getWindowHandleLocked(focusedToken);
+    sp<WindowInfoHandle> windowHandle = mWindowInfos.findWindowHandle(focusedToken);
     return isWindowOwnedBy(windowHandle, pid, uid);
 }
 
@@ -5719,7 +5741,7 @@
     return std::find_if(mInteractionConnectionTokens.begin(), mInteractionConnectionTokens.end(),
                         [&](const sp<IBinder>& connectionToken) REQUIRES(mLock) {
                             const sp<WindowInfoHandle> windowHandle =
-                                    getWindowHandleLocked(connectionToken);
+                                    mWindowInfos.findWindowHandle(connectionToken);
                             return isWindowOwnedBy(windowHandle, pid, uid);
                         }) != mInteractionConnectionTokens.end();
 }
@@ -5734,10 +5756,12 @@
     mMaximumObscuringOpacityForTouch = opacity;
 }
 
-std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
-InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
-    for (auto& [displayId, state] : mTouchStatesByDisplay) {
-        for (TouchedWindow& w : state.windows) {
+std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
+InputDispatcher::findTouchStateWindowAndDisplay(
+        const sp<IBinder>& token,
+        const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay) {
+    for (auto& [displayId, state] : touchStatesByDisplay) {
+        for (const TouchedWindow& w : state.windows) {
             if (w.windowHandle->getToken() == token) {
                 return std::make_tuple(&state, &w, displayId);
             }
@@ -5746,20 +5770,18 @@
     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);
-}
+std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
+InputDispatcher::findTouchStateWindowAndDisplay(
+        const sp<IBinder>& token,
+        std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay) {
+    auto [constTouchState, constTouchedWindow, displayId] = InputDispatcher::
+            findTouchStateWindowAndDisplay(token,
+                                           const_cast<const std::unordered_map<ui::LogicalDisplayId,
+                                                                               TouchState>&>(
+                                                   touchStatesByDisplay));
 
-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);
+    return std::make_tuple(const_cast<TouchState*>(constTouchState),
+                           const_cast<TouchedWindow*>(constTouchedWindow), displayId);
 }
 
 bool InputDispatcher::transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
@@ -5775,7 +5797,8 @@
         std::scoped_lock _l(mLock);
 
         // Find the target touch state and touched window by fromToken.
-        auto [state, touchedWindow, displayId] = findTouchStateWindowAndDisplayLocked(fromToken);
+        auto [state, touchedWindow, displayId] =
+                findTouchStateWindowAndDisplay(fromToken, mTouchStatesByDisplay);
 
         if (state == nullptr || touchedWindow == nullptr) {
             ALOGD("Touch transfer failed because from window is not being touched.");
@@ -5790,7 +5813,8 @@
         const DeviceId deviceId = *deviceIds.begin();
 
         const sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
-        const sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
+        const sp<WindowInfoHandle> toWindowHandle =
+                mWindowInfos.findWindowHandle(toToken, displayId);
         if (!toWindowHandle) {
             ALOGW("Cannot transfer touch because the transfer target window was not found.");
             return false;
@@ -5866,10 +5890,11 @@
  * Return null if there are no windows touched on that display, or if more than one foreground
  * window is being touched.
  */
-sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(
-        ui::LogicalDisplayId displayId) const {
-    auto stateIt = mTouchStatesByDisplay.find(displayId);
-    if (stateIt == mTouchStatesByDisplay.end()) {
+sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindow(
+        const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay,
+        ui::LogicalDisplayId displayId) {
+    const auto stateIt = touchStatesByDisplay.find(displayId);
+    if (stateIt == touchStatesByDisplay.end()) {
         ALOGI("No touch state on display %s", displayId.toString().c_str());
         return nullptr;
     }
@@ -5897,14 +5922,15 @@
     sp<IBinder> fromToken;
     { // acquire lock
         std::scoped_lock _l(mLock);
-        sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(destChannelToken, displayId);
+        sp<WindowInfoHandle> toWindowHandle =
+                mWindowInfos.findWindowHandle(destChannelToken, displayId);
         if (toWindowHandle == nullptr) {
             ALOGW("Could not find window associated with token=%p on display %s",
                   destChannelToken.get(), displayId.toString().c_str());
             return false;
         }
 
-        sp<WindowInfoHandle> from = findTouchedForegroundWindowLocked(displayId);
+        sp<WindowInfoHandle> from = findTouchedForegroundWindow(mTouchStatesByDisplay, displayId);
         if (from == nullptr) {
             ALOGE("Could not find a source window in %s for %p", __func__, destChannelToken.get());
             return false;
@@ -5956,7 +5982,7 @@
     std::string windowName = "None";
     if (mWindowTokenWithPointerCapture) {
         const sp<WindowInfoHandle> captureWindowHandle =
-                getWindowHandleLocked(mWindowTokenWithPointerCapture);
+                mWindowInfos.findWindowHandle(mWindowTokenWithPointerCapture);
         windowName = captureWindowHandle ? captureWindowHandle->getName().c_str()
                                          : "token has capture without window";
     }
@@ -6005,31 +6031,7 @@
         mDragState->dump(dump, INDENT2);
     }
 
-    if (!mWindowHandlesByDisplay.empty()) {
-        for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
-            dump += StringPrintf(INDENT "Display: %s\n", displayId.toString().c_str());
-            if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
-                const auto& displayInfo = it->second;
-                dump += StringPrintf(INDENT2 "logicalSize=%dx%d\n", displayInfo.logicalWidth,
-                                     displayInfo.logicalHeight);
-                displayInfo.transform.dump(dump, "transform", INDENT4);
-            } else {
-                dump += INDENT2 "No DisplayInfo found!\n";
-            }
-
-            if (!windowHandles.empty()) {
-                dump += INDENT2 "Windows:\n";
-                for (size_t i = 0; i < windowHandles.size(); i++) {
-                    dump += StringPrintf(INDENT3 "%zu: %s", i,
-                                         streamableToString(*windowHandles[i]).c_str());
-                }
-            } else {
-                dump += INDENT2 "Windows: <none>\n";
-            }
-        }
-    } else {
-        dump += INDENT "Displays: <none>\n";
-    }
+    dump += addLinePrefix(mWindowInfos.dumpDisplayAndWindowInfo(), INDENT);
 
     if (!mGlobalMonitorsByDisplay.empty()) {
         for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
@@ -6237,8 +6239,14 @@
 status_t InputDispatcher::removeInputChannel(const sp<IBinder>& connectionToken) {
     { // acquire lock
         std::scoped_lock _l(mLock);
+        std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
+        if (connection == nullptr) {
+            // Connection can be removed via socket hang up or an explicit call to
+            // 'removeInputChannel'
+            return BAD_VALUE;
+        }
 
-        status_t status = removeInputChannelLocked(connectionToken, /*notify=*/false);
+        status_t status = removeInputChannelLocked(connection, /*notify=*/false);
         if (status) {
             return status;
         }
@@ -6250,25 +6258,18 @@
     return OK;
 }
 
-status_t InputDispatcher::removeInputChannelLocked(const sp<IBinder>& connectionToken,
+status_t InputDispatcher::removeInputChannelLocked(const std::shared_ptr<Connection>& connection,
                                                    bool notify) {
-    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
-    if (connection == nullptr) {
-        // Connection can be removed via socket hang up or an explicit call to 'removeInputChannel'
-        return BAD_VALUE;
-    }
-
+    LOG_ALWAYS_FATAL_IF(connection == nullptr);
+    abortBrokenDispatchCycleLocked(connection, notify);
     removeConnectionLocked(connection);
 
     if (connection->monitor) {
-        removeMonitorChannelLocked(connectionToken);
+        removeMonitorChannelLocked(connection->getToken());
     }
 
     mLooper->removeFd(connection->inputPublisher.getChannel().getFd());
 
-    nsecs_t currentTime = now();
-    abortBrokenDispatchCycleLocked(currentTime, connection, notify);
-
     connection->status = Connection::Status::ZOMBIE;
     return OK;
 }
@@ -6301,7 +6302,8 @@
         return BAD_VALUE;
     }
 
-    auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
+    auto [statePtr, windowPtr, displayId] =
+            findTouchStateWindowAndDisplay(token, mTouchStatesByDisplay);
     if (statePtr == nullptr || windowPtr == nullptr) {
         LOG(WARNING)
                 << "Attempted to pilfer points from a channel without any on-going pointer streams."
@@ -6353,7 +6355,7 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         if (DEBUG_FOCUS) {
-            const sp<WindowInfoHandle> windowHandle = getWindowHandleLocked(windowToken);
+            const sp<WindowInfoHandle> windowHandle = mWindowInfos.findWindowHandle(windowToken);
             ALOGI("Request to %s Pointer Capture from: %s.", enabled ? "enable" : "disable",
                   windowHandle != nullptr ? windowHandle->getName().c_str()
                                           : "token without window");
@@ -6493,17 +6495,17 @@
         }
         traceWaitQueueLength(*connection);
         if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) {
-            const auto windowHandle = getWindowHandleLocked(connection->getToken());
+            const auto windowHandle = mWindowInfos.findWindowHandle(connection->getToken());
             // Only dispatch fallbacks if there is a window for the connection.
             if (windowHandle != nullptr) {
-                const auto inputTarget =
-                        createInputTargetLocked(windowHandle, InputTarget::DispatchMode::AS_IS,
-                                                dispatchEntry->targetFlags,
-                                                fallbackKeyEntry->downTime);
-                if (inputTarget.has_value()) {
-                    enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry),
-                                               *inputTarget);
-                }
+                nsecs_t downTime = fallbackKeyEntry->downTime;
+                enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry),
+                                           createInputTarget(connection, windowHandle,
+                                                             InputTarget::DispatchMode::AS_IS,
+                                                             dispatchEntry->targetFlags,
+                                                             mWindowInfos.getRawTransform(
+                                                                     *windowHandle->getInfo()),
+                                                             downTime));
             }
         }
         releaseDispatchEntry(std::move(dispatchEntry));
@@ -6557,7 +6559,7 @@
                                         ns2ms(currentWait),
                                         oldestEntry.eventEntry->getDescription().c_str());
     sp<IBinder> connectionToken = connection->getToken();
-    updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
+    updateLastAnrStateLocked(mWindowInfos.findWindowHandle(connectionToken), reason);
 
     processConnectionUnresponsiveLocked(*connection, std::move(reason));
 
@@ -6608,24 +6610,27 @@
 void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                              const KeyEntry& entry) {
     const KeyEvent event = createKeyEvent(entry);
+    std::variant<nsecs_t, KeyEntry::InterceptKeyResult> interceptResult;
     nsecs_t delay = 0;
     { // release lock
         scoped_unlock unlock(mLock);
         android::base::Timer t;
-        delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
+        interceptResult =
+                mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
         if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
             ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
                   std::to_string(t.duration().count()).c_str());
         }
     } // acquire lock
 
-    if (delay < 0) {
-        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
-    } else if (delay == 0) {
-        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
-    } else {
+    if (std::holds_alternative<KeyEntry::InterceptKeyResult>(interceptResult)) {
+        entry.interceptKeyResult = std::get<KeyEntry::InterceptKeyResult>(interceptResult);
+        return;
+    }
+
+    if (std::holds_alternative<nsecs_t>(interceptResult)) {
         entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
-        entry.interceptKeyWakeupTime = now() + delay;
+        entry.interceptKeyWakeupTime = now() + std::get<nsecs_t>(interceptResult);
     }
 }
 
@@ -6665,7 +6670,7 @@
         // The connection is a window
         ALOGW("Window %s is unresponsive: %s", connection.getInputChannelName().c_str(),
               reason.c_str());
-        const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
+        const sp<WindowInfoHandle> handle = mWindowInfos.findWindowHandle(connectionToken);
         if (handle != nullptr) {
             pid = handle->getInfo()->ownerPid;
         }
@@ -6683,7 +6688,7 @@
         pid = findMonitorPidByTokenLocked(connectionToken);
     } else {
         // The connection is a window
-        const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
+        const sp<WindowInfoHandle> handle = mWindowInfos.findWindowHandle(connectionToken);
         if (handle != nullptr) {
             pid = handle->getInfo()->ownerPid;
         }
@@ -6748,7 +6753,7 @@
             // Cancel the fallback key, but only if we still have a window for the channel.
             // It could have been removed during the policy call.
             if (*fallbackKeyCode != AKEYCODE_UNKNOWN) {
-                const auto windowHandle = getWindowHandleLocked(connection->getToken());
+                const auto windowHandle = mWindowInfos.findWindowHandle(connection->getToken());
                 if (windowHandle != nullptr) {
                     CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
                                                "application handled the original non-fallback key "
@@ -6834,7 +6839,7 @@
                 }
             }
 
-            const auto windowHandle = getWindowHandleLocked(connection->getToken());
+            const auto windowHandle = mWindowInfos.findWindowHandle(connection->getToken());
             if (windowHandle != nullptr) {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
                                            "canceling fallback, policy no longer desires it",
@@ -6976,7 +6981,7 @@
         std::scoped_lock _l(mLock);
         std::optional<FocusResolver::FocusChanges> changes =
                 mFocusResolver.setFocusedWindow(request,
-                                                getWindowHandlesLocked(
+                                                mWindowInfos.getWindowHandlesForDisplay(
                                                         ui::LogicalDisplayId{request.displayId}));
         ScopedSyntheticEventTracer traceContext(mTracer);
         if (changes) {
@@ -6994,7 +6999,7 @@
     if (changes.oldFocus) {
         const auto resolvedWindow = removedFocusedWindowHandle != nullptr
                 ? removedFocusedWindowHandle
-                : getWindowHandleLocked(changes.oldFocus, changes.displayId);
+                : mWindowInfos.findWindowHandle(changes.oldFocus, changes.displayId);
         if (resolvedWindow == nullptr) {
             LOG(FATAL) << __func__ << ": Previously focused token did not have a window";
         }
@@ -7098,13 +7103,6 @@
     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
@@ -7112,14 +7110,10 @@
 
         // Ensure that we have an entry created for all existing displays so that if a displayId has
         // no windows, we can tell that the windows were removed from the display.
-        for (const auto& [displayId, _] : mWindowHandlesByDisplay) {
-            handlesPerDisplay[displayId];
-        }
+        mWindowInfos.forEachDisplayId(
+                [&](ui::LogicalDisplayId displayId) { handlesPerDisplay[displayId]; });
 
-        mDisplayInfos.clear();
-        for (const auto& displayInfo : update.displayInfos) {
-            mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
-        }
+        mWindowInfos.setDisplayInfos(update.displayInfos);
 
         for (const auto& [displayId, handles] : handlesPerDisplay) {
             setInputWindowsLocked(handles, displayId);
@@ -7266,7 +7260,7 @@
 sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
         const sp<WindowInfoHandle>& windowHandle) const {
     const std::vector<sp<WindowInfoHandle>>& windowHandles =
-            getWindowHandlesLocked(windowHandle->getInfo()->displayId);
+            mWindowInfos.getWindowHandlesForDisplay(windowHandle->getInfo()->displayId);
     bool foundWindow = false;
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (!foundWindow && otherHandle != windowHandle) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index fade853..bca1c67 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -252,19 +252,13 @@
     // to transfer focus to a new application.
     std::shared_ptr<const EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
-    sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
-            ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
-            bool ignoreDragWindow = false) const REQUIRES(mLock);
     std::vector<InputTarget> findOutsideTargetsLocked(
             ui::LogicalDisplayId displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
             int32_t pointerId) const REQUIRES(mLock);
 
-    std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
-            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);
+    static sp<android::gui::WindowInfoHandle> findTouchedForegroundWindow(
+            const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay,
+            ui::LogicalDisplayId displayId);
 
     std::shared_ptr<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
@@ -368,24 +362,65 @@
     };
     sp<gui::WindowInfosListener> mWindowInfoListener;
 
-    std::unordered_map<ui::LogicalDisplayId /*displayId*/,
-                       std::vector<sp<android::gui::WindowInfoHandle>>>
-            mWindowHandlesByDisplay GUARDED_BY(mLock);
-    std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
-            GUARDED_BY(mLock);
+    class DispatcherWindowInfo {
+    public:
+        void setWindowHandlesForDisplay(
+                ui::LogicalDisplayId displayId,
+                std::vector<sp<android::gui::WindowInfoHandle>>&& windowHandles);
+
+        void setDisplayInfos(const std::vector<android::gui::DisplayInfo>& displayInfos);
+
+        void removeDisplay(ui::LogicalDisplayId displayId);
+
+        // Get a reference to window handles by display, return an empty vector if not found.
+        const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesForDisplay(
+                ui::LogicalDisplayId displayId) const;
+
+        void forEachWindowHandle(
+                std::function<void(const sp<android::gui::WindowInfoHandle>&)> f) const;
+
+        void forEachDisplayId(std::function<void(ui::LogicalDisplayId)> f) const;
+
+        // Get the transform for display, returns Identity-transform if display is missing.
+        ui::Transform getDisplayTransform(ui::LogicalDisplayId displayId) const;
+
+        // Get the raw transform to use for motion events going to the given window.
+        ui::Transform getRawTransform(const android::gui::WindowInfo&) const;
+
+        // Lookup for WindowInfoHandle from token and optionally a display-id. In cases where
+        // display-id is not provided lookup is done for all displays.
+        sp<android::gui::WindowInfoHandle> findWindowHandle(
+                const sp<IBinder>& windowHandleToken,
+                std::optional<ui::LogicalDisplayId> displayId = {}) const;
+
+        bool isWindowPresent(const sp<android::gui::WindowInfoHandle>& windowHandle) const;
+
+        // Returns the touched window at the given location, excluding the ignoreWindow if provided.
+        sp<android::gui::WindowInfoHandle> findTouchedWindowAt(
+                ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
+                const sp<android::gui::WindowInfoHandle> ignoreWindow = nullptr) const;
+
+        std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAt(
+                ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
+                const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay)
+                const;
+
+        std::string dumpDisplayAndWindowInfo() const;
+
+    private:
+        std::unordered_map<ui::LogicalDisplayId /*displayId*/,
+                           std::vector<sp<android::gui::WindowInfoHandle>>>
+                mWindowHandlesByDisplay;
+        std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo>
+                mDisplayInfos;
+    };
+
+    DispatcherWindowInfo mWindowInfos GUARDED_BY(mLock);
+
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
             ui::LogicalDisplayId displayId) REQUIRES(mLock);
-    // Get a reference to window handles by display, return an empty vector if not found.
-    const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesLocked(
-            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
-    ui::Transform getTransformLocked(ui::LogicalDisplayId displayId) const REQUIRES(mLock);
 
-    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
-            const sp<IBinder>& windowHandleToken,
-            std::optional<ui::LogicalDisplayId> displayId = {}) const REQUIRES(mLock);
-    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(
             ui::LogicalDisplayId displayId) const REQUIRES(mLock);
     bool canWindowReceiveMotionLocked(const sp<android::gui::WindowInfoHandle>& window,
@@ -550,10 +585,6 @@
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
-    std::optional<InputTarget> createInputTargetLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle,
-            InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
-            std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                InputTarget::DispatchMode dispatchMode,
                                ftl::Flags<InputTarget::Flags> targetFlags,
@@ -618,8 +649,7 @@
     void finishDispatchCycleLocked(nsecs_t currentTime,
                                    const std::shared_ptr<Connection>& connection, uint32_t seq,
                                    bool handled, nsecs_t consumeTime) REQUIRES(mLock);
-    void abortBrokenDispatchCycleLocked(nsecs_t currentTime,
-                                        const std::shared_ptr<Connection>& connection, bool notify)
+    void abortBrokenDispatchCycleLocked(const std::shared_ptr<Connection>& connection, bool notify)
             REQUIRES(mLock);
     void drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue);
     void releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry);
@@ -665,7 +695,7 @@
 
     // Registration.
     void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
-    status_t removeInputChannelLocked(const sp<IBinder>& connectionToken, bool notify)
+    status_t removeInputChannelLocked(const std::shared_ptr<Connection>& connection, bool notify)
             REQUIRES(mLock);
 
     // Interesting events that we might like to log or tell the framework about.
@@ -696,13 +726,15 @@
             bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
-    std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
-    findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) REQUIRES(mLock);
+    static std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
+    findTouchStateWindowAndDisplay(
+            const sp<IBinder>& token,
+            std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay);
 
-    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);
+    static std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
+    findTouchStateWindowAndDisplay(
+            const sp<IBinder>& token,
+            const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay);
 
     // Statistics gathering.
     nsecs_t mLastStatisticPushTime = 0;
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 90374f1..76f3fe0 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -77,8 +77,8 @@
     // (ignored for KeyEvents)
     float globalScaleFactor = 1.0f;
 
-    // Current display transform. Used for compatibility for raw coordinates.
-    ui::Transform displayTransform;
+    // The raw coordinate transform that's used for compatibility for MotionEvent's getRaw APIs.
+    ui::Transform rawTransform;
 
     // Event time for the first motion event (ACTION_DOWN) dispatched to this input target if
     // FLAG_SPLIT is set.
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index b885ba1..5dcd984 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -20,12 +20,14 @@
 
 #include <android-base/properties.h>
 #include <binder/IBinder.h>
+#include <dispatcher/Entry.h>
 #include <gui/InputApplication.h>
 #include <gui/PidUid.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <utils/RefBase.h>
 #include <set>
+#include <variant>
 
 namespace android {
 
@@ -106,9 +108,9 @@
                                                uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
-    virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token,
-                                                  const KeyEvent& keyEvent,
-                                                  uint32_t policyFlags) = 0;
+    virtual std::variant<nsecs_t, inputdispatcher::KeyEntry::InterceptKeyResult>
+    interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
+                                  uint32_t policyFlags) = 0;
 
     /* Allows the policy a chance to perform default processing for an unhandled key.
      * Returns an alternate key event to redispatch as a fallback, if needed. */
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index 0b17507..cc04684 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -115,13 +115,17 @@
         for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
             auto* pointerProto = outProto.add_dispatched_pointer();
             pointerProto->set_pointer_id(motion->pointerProperties[i].id);
+            const auto& coords = motion->pointerCoords[i];
             const auto rawXY =
                     MotionEvent::calculateTransformedXY(motion->source, args.rawTransform,
-                                                        motion->pointerCoords[i].getXYValue());
-            pointerProto->set_x_in_display(rawXY.x);
-            pointerProto->set_y_in_display(rawXY.y);
+                                                        coords.getXYValue());
+            if (coords.getXYValue() != rawXY) {
+                // These values are only traced if they were modified by the raw transform
+                // to save space. Trace consumers should be aware of this optimization.
+                pointerProto->set_x_in_display(rawXY.x);
+                pointerProto->set_y_in_display(rawXY.y);
+            }
 
-            const auto& coords = motion->pointerCoords[i];
             const auto coordsInWindow =
                     MotionEvent::calculateTransformedCoords(motion->source, motion->flags,
                                                             args.transform, coords);
@@ -129,6 +133,7 @@
             for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
                 const uint32_t axis = bits.clearFirstMarkedBit();
                 const float axisValueInWindow = coordsInWindow.values[axisIndex];
+                // Only values that are modified by the window transform are traced.
                 if (coords.values[axisIndex] != axisValueInWindow) {
                     auto* axisEntry = pointerProto->add_axis_value_in_window();
                     axisEntry->set_axis(axis);
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 4d6b6c7..f54b76b 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -139,8 +139,16 @@
     // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest).
     int32_t mousePointerSpeed;
 
-    // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice.
-    std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled;
+    // Displays on which all pointer scaling, including linear scaling based on the
+    // user's pointer speed setting, should be disabled for mice. This differs from
+    // disabling acceleration via the 'mousePointerAccelerationEnabled' setting, where
+    // the pointer speed setting still influences the scaling factor.
+    std::set<ui::LogicalDisplayId> displaysWithMouseScalingDisabled;
+
+    // True if the connected mouse should exhibit pointer acceleration. If false,
+    // a flat acceleration curve (linear scaling) is used, but the user's pointer
+    // speed setting still affects the scaling factor.
+    bool mousePointerAccelerationEnabled;
 
     // Velocity control parameters for touchpad pointer movements on the old touchpad stack (based
     // on TouchInputMapper).
@@ -274,12 +282,16 @@
           : virtualKeyQuietTime(0),
             defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
             mousePointerSpeed(0),
-            displaysWithMousePointerAccelerationDisabled(),
+            displaysWithMouseScalingDisabled(),
+            mousePointerAccelerationEnabled(true),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
                                              static_cast<float>(
                                                      android::os::IInputConstants::
                                                              DEFAULT_POINTER_ACCELERATION)),
-            wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
+            wheelVelocityControlParameters(1.0f, 15.0f, 50.0f,
+                                           static_cast<float>(
+                                                   android::os::IInputConstants::
+                                                           DEFAULT_MOUSE_WHEEL_ACCELERATION)),
             pointerGesturesEnabled(true),
             pointerGestureQuietInterval(100 * 1000000LL),            // 100 ms
             pointerGestureDragMinSwitchSpeed(50),                    // 50 pixels per second
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index b3cd35c..3934e78 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -79,25 +79,25 @@
     srcs: [":libinputreader_sources"],
     shared_libs: [
         "android.companion.virtualdevice.flags-aconfig-cc",
+        "libPlatformProperties",
         "libbase",
         "libcap",
         "libcrypto",
         "libcutils",
-        "libjsoncpp",
         "libinput",
+        "libjsoncpp",
         "liblog",
-        "libPlatformProperties",
         "libstatslog",
         "libstatspull",
-        "libutils",
         "libstatssocket",
+        "libutils",
     ],
     static_libs: [
         "libchrome-gestures",
-        "libui-types",
         "libexpresslog",
-        "libtextclassifier_hash_static",
         "libstatslog_express",
+        "libtextclassifier_hash_static",
+        "libui-types",
     ],
     header_libs: [
         "libbatteryservice_headers",
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index b33659c..9f584a0 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -481,15 +481,21 @@
         mPointerVelocityControl.setAccelerationEnabled(false);
         mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
         mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
-    } else {
-        mPointerVelocityControl.setAccelerationEnabled(
-                config.displaysWithMousePointerAccelerationDisabled.count(
-                        mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0);
-        mPointerVelocityControl.setCurve(
-                createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
-        mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
-        mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
+        return;
     }
+
+    bool disableAllScaling = config.displaysWithMouseScalingDisabled.count(
+                                     mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) != 0;
+
+    mPointerVelocityControl.setAccelerationEnabled(!disableAllScaling);
+
+    mPointerVelocityControl.setCurve(
+            config.mousePointerAccelerationEnabled
+                    ? createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed)
+                    : createFlatAccelerationCurve(config.mousePointerSpeed));
+
+    mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
+    mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
 }
 
 void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 5c90cbb..6efaeca 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -30,6 +30,7 @@
 
 #include <android-base/stringprintf.h>
 #include <android/input.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <input/PropertyMap.h>
@@ -47,6 +48,8 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 // --- Constants ---
 
 // Artificial latency on synthetic events created from stylus data without corresponding touch
@@ -1575,7 +1578,8 @@
                                 mLastCookedState.buttonState, mCurrentCookedState.buttonState);
 
     // Dispatch the touches either directly or by translation through a pointer on screen.
-    if (mDeviceMode == DeviceMode::POINTER) {
+    if (!input_flags::disable_touch_input_mapper_pointer_usage() &&
+        mDeviceMode == DeviceMode::POINTER) {
         for (BitSet32 idBits(mCurrentRawState.rawPointerData.touchingIdBits); !idBits.isEmpty();) {
             uint32_t id = idBits.clearFirstMarkedBit();
             const RawPointerData::Pointer& pointer =
@@ -1613,7 +1617,9 @@
         }
 
         out += dispatchPointerUsage(when, readTime, policyFlags, pointerUsage);
-    } else {
+    }
+    if (input_flags::disable_touch_input_mapper_pointer_usage() ||
+        mDeviceMode != DeviceMode::POINTER) {
         if (!mCurrentMotionAborted) {
             out += dispatchButtonRelease(when, readTime, policyFlags);
             out += dispatchHoverExit(when, readTime, policyFlags);
@@ -2251,6 +2257,23 @@
     for (uint32_t i = 0; i < currentPointerCount; i++) {
         const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
 
+        bool isHovering = in.isHovering;
+
+        // A tool MOUSE pointer is only down/touching when a mouse button is pressed.
+        if (input_flags::disable_touch_input_mapper_pointer_usage() &&
+            in.toolType == ToolType::MOUSE &&
+            !mCurrentRawState.rawPointerData.canceledIdBits.hasBit(in.id)) {
+            if (isPointerDown(mCurrentRawState.buttonState)) {
+                isHovering = false;
+                mCurrentCookedState.cookedPointerData.touchingIdBits.markBit(in.id);
+                mCurrentCookedState.cookedPointerData.hoveringIdBits.clearBit(in.id);
+            } else {
+                isHovering = true;
+                mCurrentCookedState.cookedPointerData.touchingIdBits.clearBit(in.id);
+                mCurrentCookedState.cookedPointerData.hoveringIdBits.markBit(in.id);
+            }
+        }
+
         // Size
         float touchMajor, touchMinor, toolMajor, toolMinor, size;
         switch (mCalibration.sizeCalibration) {
@@ -2340,7 +2363,7 @@
                 pressure = in.pressure * mPressureScale;
                 break;
             default:
-                pressure = in.isHovering ? 0 : 1;
+                pressure = isHovering ? 0 : 1;
                 break;
         }
 
@@ -3476,7 +3499,7 @@
     }
 
     return dispatchPointerSimple(when, readTime, policyFlags, down, hovering,
-                                 ui::LogicalDisplayId::INVALID);
+                                 getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID));
 }
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime,
@@ -3697,7 +3720,10 @@
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mDeviceMode == DeviceMode::POINTER) {
-        xCursorPosition = yCursorPosition = 0.f;
+        ALOGW_IF(pointerCount != 1,
+                 "Only single pointer events are fully supported in POINTER mode");
+        xCursorPosition = pointerCoords[0].getX();
+        yCursorPosition = pointerCoords[0].getY();
     }
     const DeviceId deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
@@ -3967,14 +3993,8 @@
 }
 
 std::optional<ui::LogicalDisplayId> TouchInputMapper::getAssociatedDisplayId() {
-    if (mParameters.hasAssociatedDisplay) {
-        if (mDeviceMode == DeviceMode::POINTER) {
-            return ui::LogicalDisplayId::INVALID;
-        } else {
-            return std::make_optional(mViewport.displayId);
-        }
-    }
-    return std::nullopt;
+    return mParameters.hasAssociatedDisplay ? std::make_optional(mViewport.displayId)
+                                            : std::nullopt;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index ef0e02f..eb4326f 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -215,7 +215,7 @@
         DISABLED,   // input is disabled
         DIRECT,     // direct mapping (touchscreen)
         NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
-        POINTER,    // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
+        POINTER,    // pointer mapping (e.g. absolute mouse, drawing tablet)
 
         ftl_last = POINTER
     };
@@ -234,6 +234,9 @@
             ftl_last = POINTER
         };
 
+        // TouchInputMapper will configure devices with INPUT_PROP_DIRECT as
+        // DeviceType::TOUCH_SCREEN, and will otherwise use DeviceType::POINTER by default.
+        // This can be overridden by IDC files, using the `touch.deviceType` config.
         DeviceType deviceType;
         bool hasAssociatedDisplay;
         bool associatedDisplayIsExternal;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 6bd949a..827076a 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -81,7 +81,6 @@
                                    const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        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.
@@ -406,7 +405,7 @@
             break;
         case GESTURES_FLING_TAP_DOWN:
             if (mCurrentClassification == MotionClassification::NONE) {
-                if (mEnableFlingStop && mFlingMayBeInProgress) {
+                if (mFlingMayBeInProgress) {
                     // The user has just touched the pad again after ending a two-finger scroll
                     // motion, which might have started a fling. We want to stop the fling, but
                     // unfortunately there's currently no API for doing so. Instead, send and
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 8d92ead..be76b61 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -104,7 +104,6 @@
 
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
-    const bool mEnableFlingStop;
     const bool mEnableNoFocusChange;
     bool mEnableSystemGestures{true};
 
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index d4e8fdf..18e0b30 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -27,6 +27,7 @@
 #include <com_android_input_flags.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <input/AccelerationCurve.h>
 #include <input/DisplayViewport.h>
 #include <input/InputEventLabels.h>
 #include <linux/input-event-codes.h>
@@ -1028,6 +1029,34 @@
                               WithCoords(0.0f, 0.0f)))));
 }
 
+TEST_F(CursorInputMapperUnitTest, PointerAccelerationDisabled) {
+    mReaderConfiguration.mousePointerAccelerationEnabled = false;
+    mReaderConfiguration.mousePointerSpeed = 3;
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    std::list<NotifyArgs> reconfigureArgs;
+
+    reconfigureArgs += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                            InputReaderConfiguration::Change::POINTER_SPEED);
+
+    std::vector<AccelerationCurveSegment> curve =
+            createFlatAccelerationCurve(mReaderConfiguration.mousePointerSpeed);
+    double baseGain = curve[0].baseGain;
+
+    std::list<NotifyArgs> motionArgs;
+    motionArgs += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    motionArgs += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    motionArgs += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    const float expectedRelX = 10 * baseGain;
+    const float expectedRelY = 20 * baseGain;
+    ASSERT_THAT(motionArgs,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE),
+                              WithRelativeMotion(expectedRelX, expectedRelY)))));
+}
+
 TEST_F(CursorInputMapperUnitTest, ConfigureAccelerationWithAssociatedViewport) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
@@ -1049,7 +1078,7 @@
     ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f);
 
     // Disable acceleration for the display, and verify that acceleration is no longer applied.
-    mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
+    mReaderConfiguration.displaysWithMouseScalingDisabled.emplace(DISPLAY_ID);
     args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                  InputReaderConfiguration::Change::POINTER_SPEED);
     args.clear();
@@ -1068,7 +1097,7 @@
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
     mReaderConfiguration.setDisplayViewports({primaryViewport});
     // Disable acceleration for the display.
-    mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
+    mReaderConfiguration.displaysWithMouseScalingDisabled.emplace(DISPLAY_ID);
 
     // Don't associate the device with the display yet.
     EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(std::nullopt));
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index db68d8a..c4257a8 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -16,6 +16,8 @@
 
 #include "FakeInputDispatcherPolicy.h"
 
+#include <variant>
+
 #include <gtest/gtest.h>
 
 namespace android {
@@ -409,12 +411,18 @@
 void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t,
                                                               int32_t, nsecs_t, uint32_t&) {}
 
-nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp<IBinder>&,
-                                                                 const KeyEvent&, uint32_t) {
+std::variant<nsecs_t, inputdispatcher::KeyEntry::InterceptKeyResult>
+FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&,
+                                                         uint32_t) {
     if (mConsumeKeyBeforeDispatching) {
-        return -1;
+        return inputdispatcher::KeyEntry::InterceptKeyResult::SKIP;
     }
+
     nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
+    if (delay == 0) {
+        return inputdispatcher::KeyEntry::InterceptKeyResult::CONTINUE;
+    }
+
     // Clear intercept state so we could dispatch the event in next wake.
     mInterceptKeyTimeout = 0ms;
     return delay;
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index a9e39d1..c387eac 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -28,11 +28,13 @@
 #include <optional>
 #include <queue>
 #include <string>
+#include <variant>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/thread_annotations.h>
 #include <binder/IBinder.h>
+#include <dispatcher/Entry.h>
 #include <gui/PidUid.h>
 #include <gui/WindowInfo.h>
 #include <input/BlockingQueue.h>
@@ -189,7 +191,8 @@
     void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override;
     void interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t, int32_t, nsecs_t,
                                        uint32_t&) override;
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override;
+    std::variant<nsecs_t, inputdispatcher::KeyEntry::InterceptKeyResult>
+    interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override;
     std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
                                                  uint32_t) override;
     void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index 3a3238a..54dc25a 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -144,10 +144,6 @@
         mInfo.setInputConfig(InputConfig::PAUSE_DISPATCHING, paused);
     }
 
-    inline void setPreventSplitting(bool preventSplitting) {
-        mInfo.setInputConfig(InputConfig::PREVENT_SPLITTING, preventSplitting);
-    }
-
     inline void setSlippery(bool slippery) {
         mInfo.setInputConfig(InputConfig::SLIPPERY, slippery);
     }
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index fe40a5e..8fa439d 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -1297,7 +1297,6 @@
 
 TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    input_flags::enable_touchpad_fling_stop(true);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 3413caa..b6e27a8 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -19,6 +19,7 @@
 #include "FakeInputDispatcherPolicy.h"
 #include "FakeInputTracingBackend.h"
 #include "FakeWindows.h"
+#include "ScopedFlagOverride.h"
 #include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
@@ -117,8 +118,12 @@
 // An arbitrary pid of the gesture monitor window
 static constexpr gui::Pid MONITOR_PID{2001};
 
+static constexpr int32_t FLAG_WINDOW_IS_OBSCURED = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+static constexpr int32_t FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
+        AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
 static constexpr int EXPECTED_WALLPAPER_FLAGS =
-        AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+        FLAG_WINDOW_IS_OBSCURED | FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
 using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
 
@@ -134,40 +139,6 @@
     return event;
 }
 
-/**
- * Provide a local override for a flag value. The value is restored when the object of this class
- * goes out of scope.
- * This class is not intended to be used directly, because its usage is cumbersome.
- * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided.
- */
-class ScopedFlagOverride {
-public:
-    ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
-          : mInitialValue(read()), mWriteValue(write) {
-        mWriteValue(value);
-    }
-    ~ScopedFlagOverride() { mWriteValue(mInitialValue); }
-
-private:
-    const bool mInitialValue;
-    std::function<void(bool)> mWriteValue;
-};
-
-typedef bool (*readFlagValueFunction)();
-typedef void (*writeFlagValueFunction)(bool);
-
-/**
- * Use this macro to locally override a flag value.
- * Example usage:
- *    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
- * Note: this works by creating a local variable in your current scope. Don't call this twice for
- * the same flag, because the variable names will clash!
- */
-#define SCOPED_FLAG_OVERRIDE(NAME, VALUE)                                  \
-    readFlagValueFunction read##NAME = com::android::input::flags::NAME;   \
-    writeFlagValueFunction write##NAME = com::android::input::flags::NAME; \
-    ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE))
-
 } // namespace
 
 // --- InputDispatcherTest ---
@@ -1211,22 +1182,17 @@
                   WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
 }
 
-class ShouldSplitTouchFixture : public InputDispatcherTest,
-                                public ::testing::WithParamInterface<bool> {};
-INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
-                         ::testing::Values(true, false));
 /**
  * A single window that receives touch (on top), and a wallpaper window underneath it.
  * The top window gets a multitouch gesture.
  * Ensure that wallpaper gets the same gesture.
  */
-TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
+TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
                                        ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
-    foregroundWindow->setPreventSplitting(GetParam());
 
     sp<FakeWindowHandle> wallpaperWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
@@ -1571,6 +1537,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.
@@ -4256,17 +4276,15 @@
 }
 
 /**
- * When events are not split, the downTime should be adjusted such that the downTime corresponds
+ * When events are 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.
+ * the event routing unless pointers are delivered to the new window.
  */
 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);
@@ -4286,13 +4304,18 @@
     // 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)));
+    // Now touch down on the new window with another pointer
+    NotifyMotionArgs pointerDownArgs =
+            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();
+    mDispatcher->notifyMotion(pointerDownArgs);
+    window1->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithPointerCount(1),
+                                      WithDownTime(downArgs.downTime)));
+    window2->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDownTime(pointerDownArgs.eventTime)));
 
     // Finish the gesture
     mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
@@ -4300,11 +4323,16 @@
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
                                       .downTime(downArgs.downTime)
                                       .build());
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDownTime(downArgs.downTime)));
+    window2->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDownTime(pointerDownArgs.eventTime)));
+
     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();
@@ -4313,13 +4341,12 @@
 /**
  * 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).
+ * If a new window appears on the screen and gets touched with the
+ * second finger, it should get the new event. At the same
+ * time, the first window should not get the pointer_down event because
+ * the touch occurred outside of its bounds.
  */
-TEST_F(InputDispatcherTest, SplitTouchesDropsEventForNonSplittableSecondWindow) {
-    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+TEST_F(InputDispatcherTest, SplitTouchesWhenWindowIsAdded) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window1 =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
@@ -4341,16 +4368,16 @@
             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
+    // Now touch down on the new 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.
+    window1->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithPointerCount(1)));
+    window2->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     // Complete the gesture
     mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
@@ -4361,6 +4388,8 @@
     // A redundant MOVE event is generated that doesn't carry any new information
     window1->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_MOVE), WithDownTime(downArgs.downTime)));
+    window2->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
                                       .downTime(downArgs.downTime)
@@ -4580,22 +4609,20 @@
  * 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) {
+TEST_F(InputDispatcherTest, SpyAboveNoInputChannelWindowSinglePointer) {
     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
+    // Another window below spy that has NO_INPUT_CHANNEL
     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(
@@ -4620,22 +4647,20 @@
  * 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) {
+TEST_F(InputDispatcherTest, SpyAboveNoInputChannelWindowTwoPointers) {
     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
+    // Another window below spy that would have NO_INPUT_CHANNEL
     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(
@@ -4667,15 +4692,10 @@
     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 spy window that does not overlap the app window.
  * - 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.
@@ -4683,34 +4703,31 @@
  * 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.
+ * The spy should not receive POINTER_DOWN(1) (since the pointer is outside of the spy).
  * Next, the first pointer (from the spy) is lifted.
- * Spy should get POINTER_UP(0).
+ * Spy should get ACTION_UP.
  * 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.
+ * However, due to the current implementation, this would still cause an ACTION_MOVE event in the
+ * app.
+ * Now, lift the remaining pointer and check that the app receives UP event.
  *
- * Finally, send a new ACTION_DOWN event to the spy and check that it's received.
+ * Finally, send a new ACTION_DOWN event to the spy and check that it gets received.
  * This test attempts to reproduce a crash in the dispatcher.
  */
-TEST_P(SpyThatPreventsSplittingWithApplicationFixture, SpyThatPreventsSplittingWithApplication) {
-    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+TEST_F(InputDispatcherTest, SpyThatPilfersAfterFirstPointerWithTwoOtherWindows) {
     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
+    // Another window below spy that has NO_INPUT_CHANNEL
     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",
@@ -4732,16 +4749,15 @@
 
     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.
+    // Second finger lands in the app. It goes to the app as ACTION_DOWN.
     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));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithPointerCount(1)));
+    appWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     // Now lift up the first pointer
     mDispatcher->notifyMotion(
@@ -4749,14 +4765,15 @@
                     .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));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    appWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithPointerCount(1)));
 
     // 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)));
+    appWindow->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)
@@ -4768,10 +4785,6 @@
     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>();
 
@@ -5725,14 +5738,12 @@
     window->assertNoEvents();
 }
 
-TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
-    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+TEST_F(InputDispatcherTest, WindowDoesNotReceiveSecondPointerOutsideOfItsBounds) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
                                        ui::LogicalDisplayId::DEFAULT);
-    // Ensure window is non-split and have some transform.
-    window->setPreventSplitting(true);
+    // Ensure window has a non-trivial transform.
     window->setWindowOffset(20, 40);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -5740,7 +5751,10 @@
               injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                     WithCoords(70, // 50 + 20
+                                                90  // 50 + 40
+                                                )));
 
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
@@ -5749,45 +5763,32 @@
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50))
                     .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
-    std::unique_ptr<MotionEvent> event = window->consumeMotionEvent();
-    ASSERT_NE(nullptr, event);
-    EXPECT_EQ(POINTER_1_DOWN, event->getAction());
-    EXPECT_EQ(70, event->getX(0));  // 50 + 20
-    EXPECT_EQ(90, event->getY(0));  // 50 + 40
-    EXPECT_EQ(-10, event->getX(1)); // -30 + 20
-    EXPECT_EQ(-10, event->getY(1)); // -50 + 40
+            << "Injection should fail because the second finger is outside of any window on the "
+               "screen.";
 }
 
 /**
- * Two windows: a splittable and a non-splittable.
- * The non-splittable window shouldn't receive any "incomplete" gestures.
- * Send the first pointer to the splittable window, and then touch the non-splittable window.
- * The second pointer should be dropped because the initial window is splittable, so it won't get
- * any pointers outside of it, and the second window is non-splittable, so it shouldn't get any
- * "incomplete" gestures.
+ * Two windows.
+ * Send the first pointer to the left window, and then touch the right window.
+ * The second pointer should generate an ACTION_DOWN in the right window.
  */
-TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) {
-    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+TEST_F(InputDispatcherTest, TwoWindowsTwoPointers) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left splittable Window",
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
                                        ui::LogicalDisplayId::DEFAULT);
-    leftWindow->setPreventSplitting(false);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right non-splittable Window",
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
                                        ui::LogicalDisplayId::DEFAULT);
-    rightWindow->setPreventSplitting(true);
     rightWindow->setFrame(Rect(100, 100, 200, 200));
     mDispatcher->onWindowInfosChanged(
             {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
 
-    // Touch down on left, splittable window
+    // Touch down on left window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
                                       .build());
@@ -5798,8 +5799,8 @@
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build());
-    leftWindow->assertNoEvents();
-    rightWindow->assertNoEvents();
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 }
 
 /**
@@ -5822,7 +5823,6 @@
             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",
@@ -5956,7 +5956,6 @@
             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",
@@ -6070,20 +6069,18 @@
 }
 
 /**
- * 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
- * POINTER_DOWN event should only go to the left window, and not to the right window.
+ * Two windows: left and right. 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 outside of all windows.
+ * The POINTER_DOWN event should be dropped.
  * This test attempts to reproduce a crash.
  */
-TEST_F(InputDispatcherTest, MultiDeviceTwoWindowsPreventSplitting) {
-    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+TEST_F(InputDispatcherTest, MultiDeviceTwoWindowsTwoPointers) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window (prevent splitting)",
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
                                        ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
-    leftWindow->setPreventSplitting(true);
 
     sp<FakeWindowHandle> rightWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
@@ -6107,14 +6104,14 @@
                                       .deviceId(deviceB)
                                       .build());
     leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
-    // Send a second pointer from device B to the right window. It shouldn't go to the right window
-    // because the left window prevents splitting.
+
+    // Send a second pointer from device B to an area outside of all windows.
     mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(deviceB)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
                                       .build());
-    leftWindow->consumeMotionPointerDown(1, WithDeviceId(deviceB));
+    // This is dropped because there's no touchable window at the location (120, 120)
 
     // Finish the gesture for both devices
     mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
@@ -6122,7 +6119,8 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
                                       .build());
-    leftWindow->consumeMotionPointerUp(1, WithDeviceId(deviceB));
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithPointerId(0, 0)));
+
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
                                       .deviceId(deviceB)
@@ -6501,19 +6499,47 @@
                                ui::LogicalDisplayId::DEFAULT, {PointF{150, 220}}));
 
     firstWindow->assertNoEvents();
-    std::unique_ptr<MotionEvent> event = secondWindow->consumeMotionEvent();
-    ASSERT_NE(nullptr, event);
-    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction());
+    secondWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN),
+                  // Ensure that the events from the "getRaw" API are in logical display
+                  // coordinates, which has an x-scale of 2 and y-scale of 4.
+                  WithRawCoords(300, 880),
+                  // Ensure that the x and y values are in the window's coordinate space.
+                  // The left-top of the second window is at (100, 200) in display space, which is
+                  // (200, 800) in the logical display space. This will be the origin of the window
+                  // space.
+                  WithCoords(100, 80)));
+}
 
-    // Ensure that the events from the "getRaw" API are in logical display coordinates.
-    EXPECT_EQ(300, event->getRawX(0));
-    EXPECT_EQ(880, event->getRawY(0));
+TEST_F(InputDispatcherDisplayProjectionTest, UseCloneLayerStackTransformForRawCoordinates) {
+    SCOPED_FLAG_OVERRIDE(use_cloned_screen_coordinates_as_raw, true);
 
-    // Ensure that the x and y values are in the window's coordinate space.
-    // The left-top of the second window is at (100, 200) in display space, which is (200, 800) in
-    // the logical display space. This will be the origin of the window space.
-    EXPECT_EQ(100, event->getX(0));
-    EXPECT_EQ(80, event->getY(0));
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    const std::array<float, 9> matrix = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0.0, 0.0, 1.0};
+    ui::Transform secondDisplayTransform;
+    secondDisplayTransform.set(matrix);
+    addDisplayInfo(SECOND_DISPLAY_ID, secondDisplayTransform);
+
+    // When a clone layer stack transform is provided for a window, we should use that as the
+    // "display transform" for input going to that window.
+    sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+    secondWindowClone->editInfo()->cloneLayerStackTransform = ui::Transform();
+    secondWindowClone->editInfo()->cloneLayerStackTransform->set(0.5, 0, 0, 0.25);
+    addWindow(secondWindowClone);
+
+    // Touch down on the clone window, and ensure its raw coordinates use
+    // the clone layer stack transform.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 SECOND_DISPLAY_ID, {PointF{150, 220}}));
+    secondWindowClone->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN),
+                  // Ensure the x and y coordinates are in the window's coordinate space.
+                  // See previous test case for calculation.
+                  WithCoords(100, 80),
+                  // Ensure the "getRaw" API uses the clone layer stack transform when it is
+                  // provided for the window. It has an x-scale of 0.5 and y-scale of 0.25.
+                  WithRawCoords(75, 55)));
 }
 
 TEST_F(InputDispatcherDisplayProjectionTest, CancelMotionWithCorrectCoordinates) {
@@ -6949,7 +6975,7 @@
                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
-TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
+TEST_P(TransferTouchFixture, TransferTouch_TwoPointers) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     PointF touchPoint = {10, 10};
@@ -6958,11 +6984,9 @@
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
                                        ui::LogicalDisplayId::DEFAULT);
-    firstWindow->setPreventSplitting(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
                                        ui::LogicalDisplayId::DEFAULT);
-    secondWindow->setPreventSplitting(true);
 
     // Add the windows to the dispatcher
     mDispatcher->onWindowInfosChanged(
@@ -8819,12 +8843,10 @@
 }
 
 /**
- * When a device reports a DOWN event, which lands in a window that supports splits, and then the
- * device then reports a POINTER_DOWN, which lands in the location of a non-existing window, then
- * the previous window should receive this event and not be dropped.
+ * First finger lands into a window, and then the second finger lands in the location of a
+ * non-existent window. The second finger should 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);
@@ -8842,13 +8864,13 @@
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
                                       .build());
 
-    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_1_DOWN)));
+    window->assertNoEvents();
 }
 
 /**
  * When deviceA reports a DOWN event, which lands in a window that supports splits, and then deviceB
  * also reports a DOWN event, which lands in the location of a non-existing window, then the
- * previous window should receive deviceB's event and it should be dropped.
+ * previous window should not receive deviceB's event and it should be dropped.
  */
 TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -12649,9 +12671,6 @@
 }
 
 TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) {
-    // Ensure window could track pointerIds if it didn't support split touch.
-    mWindow->setPreventSplitting(true);
-
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
@@ -13551,14 +13570,10 @@
 }
 
 /**
- * The spy window should not be able to affect whether or not touches are split. Only the foreground
- * windows should be allowed to control split touch.
+ * The spy window should not be able to affect whether or not touches are split.
  */
 TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) {
-    // This spy window prevents touch splitting. However, we still expect to split touches
-    // because a foreground window has not disabled splitting.
     auto spy = createSpy();
-    spy->setPreventSplitting(true);
 
     auto window = createForeground();
     window->setFrame(Rect(0, 0, 100, 100));
@@ -14684,4 +14699,219 @@
     mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
 }
 
+class InputDispatcherObscuredFlagTest : public InputDispatcherTest {
+protected:
+    std::shared_ptr<FakeApplicationHandle> mApplication;
+    std::shared_ptr<FakeApplicationHandle> mOcclusionApplication;
+    sp<FakeWindowHandle> mWindow;
+    sp<FakeWindowHandle> mOcclusionWindow;
+
+    void SetUp() override {
+        InputDispatcherTest::SetUp();
+        mDispatcher->setMaximumObscuringOpacityForTouch(0.8f);
+        mApplication = std::make_shared<FakeApplicationHandle>();
+        mOcclusionApplication = std::make_shared<FakeApplicationHandle>();
+        mWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Window", DISPLAY_ID);
+        mWindow->setOwnerInfo(WINDOW_PID, WINDOW_UID);
+
+        mOcclusionWindow = sp<FakeWindowHandle>::make(mOcclusionApplication, mDispatcher,
+                                                      "Occlusion Window", DISPLAY_ID);
+
+        mOcclusionWindow->setTouchable(false);
+        mOcclusionWindow->setTouchOcclusionMode(TouchOcclusionMode::USE_OPACITY);
+        mOcclusionWindow->setAlpha(0.7f);
+        mOcclusionWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
+    }
+};
+
+/**
+ * Two windows. An untouchable window partially occludes a touchable region below it.
+ * Use a finger to touch the bottom window.
+ * When the finger touches down in the obscured area, the motion event should always have the
+ * FLAG_WINDOW_IS_OBSCURED flag, regardless of where it is moved to. If it starts from a
+ * non-obscured area, the motion event should always with a FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag,
+ * regardless of where it is moved to.
+ */
+TEST_F(InputDispatcherObscuredFlagTest, TouchObscuredTest) {
+    mWindow->setFrame({0, 0, 100, 100});
+    mOcclusionWindow->setFrame({0, 0, 100, 50});
+
+    mDispatcher->onWindowInfosChanged(
+            {{*mOcclusionWindow->getInfo(), *mWindow->getInfo()}, {}, 0, 0});
+
+    // If the finger touch goes down in the region that is obscured.
+    // Expect the entire stream to use FLAG_WINDOW_IS_OBSCURED.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(10))
+                    .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                      WithFlags(FLAG_WINDOW_IS_OBSCURED),
+                                      WithDisplayId(DISPLAY_ID)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(60))
+                    .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                      WithFlags(FLAG_WINDOW_IS_OBSCURED),
+                                      WithDisplayId(DISPLAY_ID)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(110))
+                    .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP),
+                                      WithFlags(FLAG_WINDOW_IS_OBSCURED),
+                                      WithDisplayId(DISPLAY_ID)));
+}
+
+/**
+ * Two windows. An untouchable window partially occludes a touchable region below it.
+ * Use a finger to touch the bottom window.
+ * When the finger starts from a non-obscured area, the motion event should always have the
+ * FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag, regardless of where it is moved to.
+ */
+TEST_F(InputDispatcherObscuredFlagTest, TouchPartiallyObscuredTest) {
+    mWindow->setFrame({0, 0, 100, 100});
+    mOcclusionWindow->setFrame({0, 0, 100, 50});
+
+    mDispatcher->onWindowInfosChanged(
+            {{*mOcclusionWindow->getInfo(), *mWindow->getInfo()}, {}, 0, 0});
+
+    // If the finger touch goes down in the region that is not directly obscured by the overlay.
+    // Expect the entire stream to use FLAG_WINDOW_IS_PARTIALLY_OBSCURED.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(60))
+                    .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                      WithFlags(FLAG_WINDOW_IS_PARTIALLY_OBSCURED),
+                                      WithDisplayId(DISPLAY_ID)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(80))
+                    .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                      WithFlags(FLAG_WINDOW_IS_PARTIALLY_OBSCURED),
+                                      WithDisplayId(DISPLAY_ID)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(110))
+                    .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP),
+                                      WithFlags(FLAG_WINDOW_IS_PARTIALLY_OBSCURED),
+                                      WithDisplayId(DISPLAY_ID)));
+}
+
+/**
+ * Two windows. An untouchable window partially occludes a touchable region below it.
+ * Use the mouse to hover over the bottom window.
+ * When the hover happens over the occluded area, the window below should receive a motion
+ * event with the FLAG_WINDOW_IS_OBSCURED flag. When the hover event moves to the non-occluded area,
+ * the window below should receive a motion event with the FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag.
+ */
+TEST_F(InputDispatcherObscuredFlagTest, MouseHoverObscuredTest) {
+    mWindow->setFrame({0, 0, 100, 100});
+    mOcclusionWindow->setFrame({0, 0, 100, 40});
+
+    mDispatcher->onWindowInfosChanged(
+            {{*mOcclusionWindow->getInfo(), *mWindow->getInfo()}, {}, 0, 0});
+
+    // TODO(b/328160937): The window should receive a motion event with the FLAG_WINDOW_IS_OBSCURED
+    // flag.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(20))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithFlags(0)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(30))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithFlags(0)));
+
+    // TODO(b/328160937): The window should receive a motion event with the
+    // FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithFlags(0)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(60))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithFlags(0)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(110))
+                    .build());
+
+    // TODO(b/328160937): The window should receive a HOVER_EXIT with the
+    //  FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag. The cause of the current issue is that we moved the
+    //  mouse to a location where there are no windows, so the HOVER_EXIT event cannot be generated.
+    mWindow->assertNoEvents();
+}
+
+/**
+ * Two windows. An untouchable window partially occludes a touchable region below it.
+ * Use the stylus to hover over the bottom window.
+ * When the hover happens over the occluded area, the window below should receive a motion
+ * event with the FLAG_WINDOW_IS_OBSCURED flag. When the hover event moves to the non-occluded area,
+ * the window below should receive a motion event with the FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag.
+ */
+TEST_F(InputDispatcherObscuredFlagTest, StylusHoverObscuredTest) {
+    mWindow->setFrame({0, 0, 100, 100});
+    mOcclusionWindow->setFrame({0, 0, 100, 40});
+
+    mDispatcher->onWindowInfosChanged(
+            {{*mOcclusionWindow->getInfo(), *mWindow->getInfo()}, {}, 0, 0});
+
+    // TODO(b/328160937): The window should receive a motion event with the FLAG_WINDOW_IS_OBSCURED
+    // flag.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(20))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithFlags(0)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(30))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithFlags(0)));
+
+    // TODO(b/328160937): The window should receive a motion event with the
+    // FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(50))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithFlags(0)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(60))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithFlags(0)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(70))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithFlags(0)));
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 9d2256f..470e65b 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -28,6 +28,7 @@
 #include <MultiTouchInputMapper.h>
 #include <NotifyArgsBuilders.h>
 #include <PeripheralController.h>
+#include <ScopedFlagOverride.h>
 #include <SingleTouchInputMapper.h>
 #include <TestEventMatchers.h>
 #include <TestInputListener.h>
@@ -2474,10 +2475,10 @@
     const auto syncTime = std::chrono::system_clock::now();
     // After 72 ms, the event *will* be generated. If we wait the full 72 ms to check that NO event
     // is generated in that period, there will be a race condition between the event being generated
-    // and the test's wait timeout expiring. Thus, we wait for a shorter duration in the test, which
-    // will reduce the liklihood of the race condition occurring.
-    const auto waitUntilTimeForNoEvent =
-            syncTime + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT / 2));
+    // and the test's wait timeout expiring. Thus, we wait for a shorter duration in the test to
+    // ensure the event is not immediately generated, which should reduce the liklihood of the race
+    // condition occurring.
+    const auto waitUntilTimeForNoEvent = syncTime + std::chrono::milliseconds(1);
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntilTimeForNoEvent));
 
@@ -4526,6 +4527,10 @@
 
     NotifyMotionArgs motionArgs;
 
+    // Hold down the mouse button for the duration of the test, since the mouse tools require
+    // the button to be pressed to make sure they are not hovering.
+    processKey(mapper, BTN_MOUSE, 1);
+
     // default tool type is finger
     processDown(mapper, 100, 200);
     processSync(mapper);
@@ -4533,6 +4538,9 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)));
+
     // eraser
     processKey(mapper, BTN_TOOL_RUBBER, 1);
     processSync(mapper);
@@ -7175,6 +7183,10 @@
 
     NotifyMotionArgs motionArgs;
 
+    // Hold down the mouse button for the duration of the test, since the mouse tools require
+    // the button to be pressed to make sure they are not hovering.
+    processKey(mapper, BTN_MOUSE, 1);
+
     // default tool type is finger
     processId(mapper, 1);
     processPosition(mapper, 100, 200);
@@ -7183,6 +7195,9 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
 
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)));
+
     // eraser
     processKey(mapper, BTN_TOOL_RUBBER, 1);
     processSync(mapper);
@@ -7520,6 +7535,7 @@
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, true);
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
 
     prepareDisplay(ui::ROTATION_0);
@@ -7532,9 +7548,9 @@
     processPosition(mapper, 100, 100);
     processSync(mapper);
 
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(ui::LogicalDisplayId::INVALID, motionArgs.displayId);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithDisplayId(DISPLAY_ID),
+                  WithSource(AINPUT_SOURCE_MOUSE), WithToolType(ToolType::FINGER))));
 }
 
 /**
@@ -8604,6 +8620,8 @@
  * fingers start to move downwards, the gesture should be swipe.
  */
 TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, false);
+
     // The min freeform gesture width is 25units/mm x 30mm = 750
     // which is greater than fraction of the diagnal length of the touchpad (349).
     // Thus, MaxSwipWidth is 750.
@@ -8664,6 +8682,8 @@
  * the gesture should be swipe.
  */
 TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, false);
+
     // The min freeform gesture width is 5units/mm x 30mm = 150
     // which is greater than fraction of the diagnal length of the touchpad (349).
     // Thus, MaxSwipWidth is the fraction of the diagnal length, 349.
@@ -8723,6 +8743,8 @@
  * freeform gestures after two fingers start to move downwards.
  */
 TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, false);
+
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
@@ -8818,6 +8840,8 @@
 }
 
 TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, false);
+
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
@@ -8864,6 +8888,8 @@
 }
 
 TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, false);
+
     preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index 9a6b266..d15048d 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -23,6 +23,7 @@
 
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
+#include "ScopedFlagOverride.h"
 #include "TestEventMatchers.h"
 
 #define TAG "MultiTouchpadInputMapperUnit_test"
@@ -30,6 +31,7 @@
 namespace android {
 
 using testing::_;
+using testing::AllOf;
 using testing::IsEmpty;
 using testing::Return;
 using testing::SetArgPointee;
@@ -266,4 +268,94 @@
                         VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
 }
 
+class MultiTouchInputMapperPointerModeUnitTest : public MultiTouchInputMapperUnitTest {
+protected:
+    void SetUp() override {
+        MultiTouchInputMapperUnitTest::SetUp();
+
+        // TouchInputMapper goes into POINTER mode whenever INPUT_PROP_DIRECT is not set.
+        EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT))
+                .WillRepeatedly(Return(false));
+
+        mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
+                                                           mFakePolicy->getReaderConfiguration());
+    }
+};
+
+TEST_F(MultiTouchInputMapperPointerModeUnitTest, MouseToolOnlyDownWhenMouseButtonsAreDown) {
+    SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, true);
+
+    std::list<NotifyArgs> args;
+
+    // Set the tool type to mouse.
+    args += processKey(BTN_TOOL_MOUSE, 1);
+
+    args += processPosition(100, 100);
+    args += processId(1);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithToolType(ToolType::MOUSE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithToolType(ToolType::MOUSE)))));
+
+    // Setting BTN_TOUCH does not make a mouse pointer go down.
+    args = processKey(BTN_TOUCH, 1);
+    args += processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+
+    // The mouse button is pressed, so the mouse goes down.
+    args = processKey(BTN_MOUSE, 1);
+    args += processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithToolType(ToolType::MOUSE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithToolType(ToolType::MOUSE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithToolType(ToolType::MOUSE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY)))));
+
+    // The mouse button is released, so the mouse starts hovering.
+    args = processKey(BTN_MOUSE, 0);
+    args += processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithToolType(ToolType::MOUSE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithToolType(ToolType::MOUSE), WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithToolType(ToolType::MOUSE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithToolType(ToolType::MOUSE)))));
+
+    // Change the tool type so that it is no longer a mouse.
+    // The default tool type is finger, and the finger is already down.
+    args = processKey(BTN_TOOL_MOUSE, 0);
+    args += processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithToolType(ToolType::MOUSE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithToolType(ToolType::FINGER)))));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 27da3d3..1ca2998 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -2633,16 +2633,14 @@
                            ui::ROTATION_0),
     };
 
-    std::unordered_map<ui::LogicalDisplayId, std::vector<PointerChoreographer::AdjacentDisplay>>
-            mTopology{
-                    {DISPLAY_CENTER_ID,
-                     {{DISPLAY_TOP_ID, PointerChoreographer::DisplayPosition::TOP, 10.0f},
-                      {DISPLAY_RIGHT_ID, PointerChoreographer::DisplayPosition::RIGHT, 10.0f},
-                      {DISPLAY_BOTTOM_ID, PointerChoreographer::DisplayPosition::BOTTOM, 10.0f},
-                      {DISPLAY_LEFT_ID, PointerChoreographer::DisplayPosition::LEFT, 10.0f},
-                      {DISPLAY_TOP_RIGHT_CORNER_ID, PointerChoreographer::DisplayPosition::RIGHT,
-                       -90.0f}}},
-            };
+    DisplayTopologyGraph mTopology{DISPLAY_CENTER_ID,
+                                   {{DISPLAY_CENTER_ID,
+                                     {{DISPLAY_TOP_ID, DisplayTopologyPosition::TOP, 10.0f},
+                                      {DISPLAY_RIGHT_ID, DisplayTopologyPosition::RIGHT, 10.0f},
+                                      {DISPLAY_BOTTOM_ID, DisplayTopologyPosition::BOTTOM, 10.0f},
+                                      {DISPLAY_LEFT_ID, DisplayTopologyPosition::LEFT, 10.0f},
+                                      {DISPLAY_TOP_RIGHT_CORNER_ID, DisplayTopologyPosition::RIGHT,
+                                       -90.0f}}}}};
 
 private:
     DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
@@ -2706,6 +2704,11 @@
         testing::Values(
                 // Note: Upon viewport transition cursor will be positioned at the boundary of the
                 // destination, as we drop any unconsumed delta.
+                std::make_tuple("PrimaryDisplayIsDefault", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(50, 50) /* initial x/y */, vec2(0, 0) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(50, 50) /* destination x/y */),
                 std::make_tuple("UnchangedDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
                                 ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
                                 vec2(25, 25) /* delta x/y */,
diff --git a/services/inputflinger/tests/ScopedFlagOverride.h b/services/inputflinger/tests/ScopedFlagOverride.h
new file mode 100644
index 0000000..883673c
--- /dev/null
+++ b/services/inputflinger/tests/ScopedFlagOverride.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <com_android_input_flags.h>
+#include <functional>
+
+namespace android {
+
+/**
+ * Provide a local override for a flag value. The value is restored when the object of this class
+ * goes out of scope.
+ * This class is not intended to be used directly, because its usage is cumbersome.
+ * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided.
+ */
+class ScopedFlagOverride {
+public:
+    ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
+          : mInitialValue(read()), mWriteValue(write) {
+        mWriteValue(value);
+    }
+    ~ScopedFlagOverride() { mWriteValue(mInitialValue); }
+
+private:
+    const bool mInitialValue;
+    std::function<void(bool)> mWriteValue;
+};
+
+typedef bool (*ReadFlagValueFunction)();
+typedef void (*WriteFlagValueFunction)(bool);
+
+/**
+ * Use this macro to locally override a flag value.
+ * Example usage:
+ *    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+ * Note: this works by creating a local variable in your current scope. Don't call this twice for
+ * the same flag, because the variable names will clash!
+ */
+#define SCOPED_FLAG_OVERRIDE(NAME, VALUE)                                  \
+    ReadFlagValueFunction read##NAME = com::android::input::flags::NAME;   \
+    WriteFlagValueFunction write##NAME = com::android::input::flags::NAME; \
+    ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE))
+
+} // namespace android
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 7078e49..7fb8895 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -32,6 +32,17 @@
 
 namespace android {
 
+namespace {
+
+template <typename T>
+static bool valuesMatch(T value1, T value2) {
+    if constexpr (std::is_floating_point_v<T>) {
+        return std::abs(value1 - value2) < EPSILON;
+    } else {
+        return value1 == value2;
+    }
+}
+
 struct PointF {
     float x;
     float y;
@@ -42,6 +53,8 @@
     return std::string("(") + std::to_string(p.x) + ", " + std::to_string(p.y) + ")";
 }
 
+} // namespace
+
 /// Source
 class WithSourceMatcher {
 public:
@@ -706,8 +719,8 @@
         }
 
         const PointerCoords& coords = event.pointerCoords[mPointerIndex];
-        bool matches = mRelX == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) &&
-                mRelY == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        bool matches = valuesMatch(mRelX, coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X)) &&
+                valuesMatch(mRelY, coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
         if (!matches) {
             *os << "expected relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
                 << mPointerIndex << ", but got ("
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index 79a5ff6..31db2fe 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -76,7 +76,6 @@
     window.setDupTouchToWallpaper(fdp.ConsumeBool());
     window.setIsWallpaper(fdp.ConsumeBool());
     window.setVisible(fdp.ConsumeBool());
-    window.setPreventSplitting(fdp.ConsumeBool());
     const bool isTrustedOverlay = fdp.ConsumeBool();
     window.setTrustedOverlay(isTrustedOverlay);
     if (isTrustedOverlay) {
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index abeb2a9..77bf145 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -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/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index e32cc02..fd58191 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -53,7 +53,7 @@
     createLayerFECompositionState() = 0;
 
     virtual HWComposer& getHwComposer() const = 0;
-    virtual void setHwComposer(std::unique_ptr<HWComposer>) = 0;
+    virtual void setHwComposer(HWComposer*) = 0;
 
     virtual renderengine::RenderEngine& getRenderEngine() const = 0;
     virtual void setRenderEngine(renderengine::RenderEngine*) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index cda4edc..e876693 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -36,6 +36,10 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+namespace aidl::android::hardware::graphics::composer3 {
+enum class Composition;
+}
+
 namespace android {
 
 class Fence;
@@ -121,6 +125,8 @@
 
         // True if layers with 170M dataspace should be overridden to sRGB.
         const bool treat170mAsSrgb;
+
+        std::shared_ptr<gui::DisplayLuts> luts;
     };
 
     // A superset of LayerSettings required by RenderEngine to compose a layer
@@ -131,6 +137,9 @@
 
         // Currently latched frame number, 0 if invalid.
         uint64_t frameNumber = 0;
+
+        // layer serial number, -1 if invalid.
+        int32_t sequence = -1;
     };
 
     // Describes the states of the release fence. Checking the states allows checks
@@ -173,6 +182,11 @@
     // Whether the layer should be rendered with rounded corners.
     virtual bool hasRoundedCorners() const = 0;
     virtual void setWasClientComposed(const sp<Fence>&) {}
+    virtual void setHwcCompositionType(
+            aidl::android::hardware::graphics::composer3::Composition) = 0;
+    virtual aidl::android::hardware::graphics::composer3::Composition getHwcCompositionType()
+            const = 0;
+
     virtual const gui::LayerMetadata* getMetadata() const = 0;
     virtual const gui::LayerMetadata* getRelativeMetadata() const = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index 45208dd..2992b6d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -31,7 +31,7 @@
             override;
 
     HWComposer& getHwComposer() const override;
-    void setHwComposer(std::unique_ptr<HWComposer>) override;
+    void setHwComposer(HWComposer*) override;
 
     renderengine::RenderEngine& getRenderEngine() const override;
     void setRenderEngine(renderengine::RenderEngine*) override;
@@ -59,7 +59,7 @@
     void setNeedsAnotherUpdateForTest(bool);
 
 private:
-    std::unique_ptr<HWComposer> mHwComposer;
+    HWComposer* mHwComposer;
     renderengine::RenderEngine* mRenderEngine;
     std::shared_ptr<TimeStats> mTimeStats;
     bool mNeedsAnotherUpdate = false;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index a1b7282..bb1a222 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -37,7 +37,7 @@
                  std::unique_ptr<compositionengine::LayerFECompositionState>());
 
     MOCK_CONST_METHOD0(getHwComposer, HWComposer&());
-    MOCK_METHOD1(setHwComposer, void(std::unique_ptr<HWComposer>));
+    MOCK_METHOD1(setHwComposer, void(HWComposer*));
 
     MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&());
     MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*));
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 272fa3e..7744b8b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -59,6 +59,10 @@
     MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*());
     MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*());
     MOCK_METHOD0(onPictureProfileCommitted, void());
+    MOCK_METHOD(void, setHwcCompositionType,
+                (aidl::android::hardware::graphics::composer3::Composition), (override));
+    MOCK_METHOD(aidl::android::hardware::graphics::composer3::Composition, getHwcCompositionType,
+                (), (const, override));
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index cfcce47..989f8e3 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -58,11 +58,11 @@
 }
 
 HWComposer& CompositionEngine::getHwComposer() const {
-    return *mHwComposer.get();
+    return *mHwComposer;
 }
 
-void CompositionEngine::setHwComposer(std::unique_ptr<HWComposer> hwComposer) {
-    mHwComposer = std::move(hwComposer);
+void CompositionEngine::setHwComposer(HWComposer* hwComposer) {
+    mHwComposer = hwComposer;
 }
 
 renderengine::RenderEngine& CompositionEngine::getRenderEngine() const {
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 734d764..de1d13a 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1564,7 +1564,9 @@
                                        .clearContent = !clientComposition,
                                        .blurSetting = blurSetting,
                                        .whitePointNits = layerState.whitePointNits,
-                                       .treat170mAsSrgb = outputState.treat170mAsSrgb};
+                                       .treat170mAsSrgb = outputState.treat170mAsSrgb,
+                                       .luts = layer->getState().hwc ? layer->getState().hwc->luts
+                                                                     : nullptr};
                 if (auto clientCompositionSettings =
                             layerFE.prepareClientComposition(targetSettings)) {
                     clientCompositionLayers.push_back(std::move(*clientCompositionSettings));
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 96b86d5..9b66f01 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -867,6 +867,7 @@
     if (outputDependentState.hwc->hwcCompositionType != requestedCompositionType ||
         (outputDependentState.hwc->layerSkipped && !skipLayer)) {
         outputDependentState.hwc->hwcCompositionType = requestedCompositionType;
+        getLayerFE().setHwcCompositionType(requestedCompositionType);
 
         if (auto error = hwcLayer->setCompositionType(requestedCompositionType);
             error != hal::Error::NONE) {
@@ -964,6 +965,7 @@
     }
 
     hwcState.hwcCompositionType = compositionType;
+    getLayerFE().setHwcCompositionType(compositionType);
 }
 
 void OutputLayer::prepareForDeviceLayerRequests() {
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 3e0c390..ad65c44 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -61,7 +61,7 @@
 
 TEST_F(CompositionEngineTest, canSetHWComposer) {
     android::mock::HWComposer* hwc = new StrictMock<android::mock::HWComposer>();
-    mEngine.setHwComposer(std::unique_ptr<android::HWComposer>(hwc));
+    mEngine.setHwComposer(static_cast<android::HWComposer*>(hwc));
 
     EXPECT_EQ(hwc, &mEngine.getHwComposer());
 }
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 8529c72..366d3f4 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -686,6 +686,36 @@
     return error;
 }
 
+Error AidlComposer::getLayerPresentFences(Display display, std::vector<Layer>* outLayers,
+                                          std::vector<int>* outFences,
+                                          std::vector<int64_t>* outLatenciesNanos) {
+    Error error = Error::NONE;
+    std::vector<PresentFence::LayerPresentFence> fences;
+    {
+        mMutex.lock_shared();
+        if (auto reader = getReader(display)) {
+            fences = reader->get().takeLayerPresentFences(translate<int64_t>(display));
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
+    }
+
+    outLayers->reserve(fences.size());
+    outFences->reserve(fences.size());
+    outLatenciesNanos->reserve(fences.size());
+
+    for (auto& fence : fences) {
+        outLayers->emplace_back(translate<Layer>(fence.layer));
+        // take ownership
+        const int fenceOwner = fence.bufferFence.get();
+        *fence.bufferFence.getR() = -1;
+        outFences->emplace_back(fenceOwner);
+        outLatenciesNanos->emplace_back(fence.bufferLatencyNanos);
+    }
+    return error;
+}
+
 Error AidlComposer::presentDisplay(Display display, int* outPresentFence) {
     const auto displayId = translate<int64_t>(display);
     SFTRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId);
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 82006f4..79e3349 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -106,6 +106,10 @@
     Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                            std::vector<int>* outReleaseFences) override;
 
+    Error getLayerPresentFences(Display display, std::vector<Layer>* outLayers,
+                                std::vector<int>* outFences,
+                                std::vector<int64_t>* outLatenciesNanos) override;
+
     Error presentDisplay(Display display, int* outPresentFence) override;
 
     Error setActiveConfig(Display display, Config config) override;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 6e431bb..018ee6e 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -157,6 +157,10 @@
     virtual Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                                    std::vector<int>* outReleaseFences) = 0;
 
+    virtual Error getLayerPresentFences(Display display, std::vector<Layer>* outLayers,
+                                        std::vector<int>* outFences,
+                                        std::vector<int64_t>* outLatenciesNanos) = 0;
+
     virtual Error presentDisplay(Display display, int* outPresentFence) = 0;
 
     virtual Error setActiveConfig(Display display, Config config) = 0;
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 99a67e4..252c6b6 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -629,7 +629,7 @@
         auto layer = getLayerById(layerIds[i]);
         if (layer) {
             auto& layerLut = tmpLuts[i];
-            if (layerLut.luts.pfd.get() > 0 && layerLut.luts.offsets.has_value()) {
+            if (layerLut.luts.pfd.get() >= 0 && layerLut.luts.offsets.has_value()) {
                 const auto& offsets = layerLut.luts.offsets.value();
                 std::vector<std::pair<int32_t, LutProperties>> lutOffsetsAndProperties;
                 lutOffsetsAndProperties.reserve(offsets.size());
@@ -640,7 +640,15 @@
                 lutFileDescriptorMapper.emplace_or_replace(layer.get(),
                                                            ndk::ScopedFileDescriptor(
                                                                    layerLut.luts.pfd.release()));
+            } else {
+                ALOGE("getRequestedLuts: invalid luts on layer %" PRIu64 " found"
+                      " on display %" PRIu64 ". pfd.get()=%d, offsets.has_value()=%d",
+                      layerIds[i], mId, layerLut.luts.pfd.get(), layerLut.luts.offsets.has_value());
             }
+        } else {
+            ALOGE("getRequestedLuts: invalid layer %" PRIu64 " found"
+                  " on display %" PRIu64,
+                  layerIds[i], mId);
         }
     }
 
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index ec15539..fc317f3 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -590,6 +590,11 @@
     return Error::NONE;
 }
 
+Error HidlComposer::getLayerPresentFences(Display, std::vector<Layer>*, std::vector<int>*,
+                                          std::vector<int64_t>*) {
+    return Error::UNSUPPORTED;
+}
+
 Error HidlComposer::presentDisplay(Display display, int* outPresentFence) {
     SFTRACE_NAME("HwcPresentDisplay");
     mWriter.selectDisplay(display);
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index cacdb8c..86ca4b1 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -214,6 +214,10 @@
     Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                            std::vector<int>* outReleaseFences) override;
 
+    Error getLayerPresentFences(Display display, std::vector<Layer>* outLayers,
+                                std::vector<int>* outFences,
+                                std::vector<int64_t>* outLatenciesNanos) override;
+
     Error presentDisplay(Display display, int* outPresentFence) override;
 
     Error setActiveConfig(Display display, Config config) override;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index 47d0041..4fdbae1 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -102,6 +102,8 @@
         // Returns true if the node is a clone.
         bool isClone() const { return !mirrorRootIds.empty(); }
 
+        TraversalPath getClonedFrom() const { return {.id = id, .variant = variant}; }
+
         bool operator==(const TraversalPath& other) const {
             return id == other.id && mirrorRootIds == other.mirrorRootIds;
         }
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index f1091a6..d369403 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -182,8 +182,8 @@
     }
 }
 
-void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState>& transactions,
-                                              bool ignoreUnknownLayers) {
+void LayerLifecycleManager::applyTransactions(
+        const std::vector<QueuedTransactionState>& transactions, bool ignoreUnknownLayers) {
     for (const auto& transaction : transactions) {
         for (const auto& resolvedComposerState : transaction.states) {
             const auto& clientState = resolvedComposerState.state;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 330da9a..072be35 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -16,8 +16,8 @@
 
 #pragma once
 
+#include "QueuedTransactionState.h"
 #include "RequestedLayerState.h"
-#include "TransactionState.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -43,7 +43,8 @@
     // the layers it is unreachable. When using the LayerLifecycleManager for layer trace
     // generation we may encounter layers which are known because we don't have an explicit
     // lifecycle. Ignore these errors while we have to interop with legacy.
-    void applyTransactions(const std::vector<TransactionState>&, bool ignoreUnknownLayers = false);
+    void applyTransactions(const std::vector<QueuedTransactionState>&,
+                           bool ignoreUnknownLayers = false);
     // Ignore unknown handles when iteroping with legacy front end. In the old world, we
     // would create child layers which are not necessary with the new front end. This means
     // we will get notified for handle changes that don't exist in the new front end.
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 367132c..523ef7b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -18,12 +18,17 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
-#include "LayerSnapshot.h"
+#include <PowerAdvisor/Workload.h>
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <gui/LayerState.h>
+
 #include "Layer.h"
+#include "LayerSnapshot.h"
 
 namespace android::surfaceflinger::frontend {
 
 using namespace ftl::flag_operators;
+using namespace aidl::android::hardware::graphics::composer3;
 
 namespace {
 
@@ -398,9 +403,13 @@
     if (forceUpdate || requested.what & layer_state_t::eSidebandStreamChanged) {
         sidebandStream = requested.sidebandStream;
     }
-    if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) {
-        shadowSettings.length = requested.shadowRadius;
+    if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged ||
+        requested.what & layer_state_t::eClientDrawnShadowsChanged) {
+        shadowSettings.length =
+                requested.clientDrawnShadowRadius > 0 ? 0.f : requested.shadowRadius;
+        shadowSettings.clientDrawnLength = requested.clientDrawnShadowRadius;
     }
+
     if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) {
         frameRateSelectionPriority = requested.frameRateSelectionPriority;
     }
@@ -528,4 +537,49 @@
     }
 }
 
+char LayerSnapshot::classifyCompositionForDebug(Composition compositionType) const {
+    if (!isVisible) {
+        return '.';
+    }
+
+    switch (compositionType) {
+        case Composition::INVALID:
+            return 'i';
+        case Composition::SOLID_COLOR:
+            return 'c';
+        case Composition::CURSOR:
+            return 'u';
+        case Composition::SIDEBAND:
+            return 'd';
+        case Composition::DISPLAY_DECORATION:
+            return 'a';
+        case Composition::REFRESH_RATE_INDICATOR:
+            return 'r';
+        case Composition::CLIENT:
+        case Composition::DEVICE:
+            break;
+    }
+
+    char code = '.'; // Default to invisible
+    if (hasBufferOrSidebandStream()) {
+        code = 'b';
+    } else if (fillsColor()) {
+        code = 'c'; // Solid color
+    } else if (hasBlur()) {
+        code = 'l'; // Blur
+    } else if (hasProtectedContent) {
+        code = 'p'; // Protected content
+    } else if (drawShadows()) {
+        code = 's'; // Shadow
+    } else if (roundedCorner.hasRoundedCorners()) {
+        code = 'r'; // Rounded corners
+    }
+
+    if (compositionType == Composition::CLIENT) {
+        return static_cast<char>(std::toupper(code));
+    } else {
+        return code;
+    }
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index b8df3ed..04b9f3b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <PowerAdvisor/Workload.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <renderengine/LayerSettings.h>
 #include "DisplayHardware/ComposerHal.h"
@@ -28,16 +29,23 @@
 
 struct RoundedCornerState {
     RoundedCornerState() = default;
-    RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
-          : cropRect(cropRect), radius(radius) {}
 
     // Rounded rectangle in local layer coordinate space.
     FloatRect cropRect = FloatRect();
-    // Radius of the rounded rectangle.
+    // Radius of the rounded rectangle for composition
     vec2 radius;
+    // Requested radius of the rounded rectangle
+    vec2 requestedRadius;
+    // Radius drawn by client for the rounded rectangle
+    vec2 clientDrawnRadius;
+    bool hasClientDrawnRadius() const {
+        return clientDrawnRadius.x > 0.0f && clientDrawnRadius.y > 0.0f;
+    }
+    bool hasRequestedRadius() const { return requestedRadius.x > 0.0f && requestedRadius.y > 0.0f; }
     bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
     bool operator==(RoundedCornerState const& rhs) const {
-        return cropRect == rhs.cropRect && radius == rhs.radius;
+        return cropRect == rhs.cropRect && radius == rhs.radius &&
+                clientDrawnRadius == rhs.clientDrawnRadius;
     }
 };
 
@@ -152,6 +160,10 @@
     friend std::ostream& operator<<(std::ostream& os, const LayerSnapshot& obj);
     void merge(const RequestedLayerState& requested, bool forceUpdate, bool displayChanges,
                bool forceFullDamage, uint32_t displayRotationFlags);
+    // Returns a char summarizing the composition request
+    // This function tries to maintain parity with planner::Plan chars.
+    char classifyCompositionForDebug(
+            aidl::android::hardware::graphics::composer3::Composition compositionType) const;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 022588d..7289e2f 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -16,6 +16,8 @@
 
 // #define LOG_NDEBUG 0
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include "FrontEnd/LayerSnapshot.h"
+#include "ui/Transform.h"
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
@@ -25,6 +27,7 @@
 #include <common/FlagManager.h>
 #include <common/trace.h>
 #include <ftl/small_map.h>
+#include <math/vec2.h>
 #include <ui/DisplayMap.h>
 #include <ui/FloatRect.h>
 
@@ -932,7 +935,8 @@
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eCornerRadiusChanged ||
         snapshot.changes.any(RequestedLayerState::Changes::Geometry |
-                             RequestedLayerState::Changes::BufferUsageFlags)) {
+                             RequestedLayerState::Changes::BufferUsageFlags) ||
+        snapshot.clientChanges & layer_state_t::eClientDrawnCornerRadiusChanged) {
         updateRoundedCorner(snapshot, requested, parentSnapshot, args);
     }
 
@@ -972,19 +976,27 @@
     }
     snapshot.roundedCorner = RoundedCornerState();
     RoundedCornerState parentRoundedCorner;
-    if (parentSnapshot.roundedCorner.hasRoundedCorners()) {
+    if (parentSnapshot.roundedCorner.hasRequestedRadius()) {
         parentRoundedCorner = parentSnapshot.roundedCorner;
         ui::Transform t = snapshot.localTransform.inverse();
         parentRoundedCorner.cropRect = t.transform(parentRoundedCorner.cropRect);
         parentRoundedCorner.radius.x *= t.getScaleX();
         parentRoundedCorner.radius.y *= t.getScaleY();
+        parentRoundedCorner.requestedRadius.x *= t.getScaleX();
+        parentRoundedCorner.requestedRadius.y *= t.getScaleY();
     }
 
     FloatRect layerCropRect = snapshot.croppedBufferSize;
-    const vec2 radius(requested.cornerRadius, requested.cornerRadius);
-    RoundedCornerState layerSettings(layerCropRect, radius);
-    const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty();
-    const bool parentRoundedCornerValid = parentRoundedCorner.hasRoundedCorners();
+    const vec2 requestedRadius(requested.cornerRadius, requested.cornerRadius);
+    const vec2 clientDrawnRadius(requested.clientDrawnCornerRadius,
+                                 requested.clientDrawnCornerRadius);
+    RoundedCornerState layerSettings;
+    layerSettings.cropRect = layerCropRect;
+    layerSettings.requestedRadius = requestedRadius;
+    layerSettings.clientDrawnRadius = clientDrawnRadius;
+
+    const bool layerSettingsValid = layerSettings.hasRequestedRadius() && !layerCropRect.isEmpty();
+    const bool parentRoundedCornerValid = parentRoundedCorner.hasRequestedRadius();
     if (layerSettingsValid && parentRoundedCornerValid) {
         // 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
@@ -1002,6 +1014,14 @@
     } else if (parentRoundedCornerValid) {
         snapshot.roundedCorner = parentRoundedCorner;
     }
+
+    if (snapshot.roundedCorner.requestedRadius.x == requested.clientDrawnCornerRadius) {
+        // If the client drawn radius matches the requested radius, then surfaceflinger
+        // does not need to draw rounded corners for this layer
+        snapshot.roundedCorner.radius = vec2(0.f, 0.f);
+    } else {
+        snapshot.roundedCorner.radius = snapshot.roundedCorner.requestedRadius;
+    }
 }
 
 /**
@@ -1205,13 +1225,27 @@
     snapshot.inputInfo.contentSize = {snapshot.croppedBufferSize.getHeight(),
                                       snapshot.croppedBufferSize.getWidth()};
 
-    // 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.
+    snapshot.inputInfo.cloneLayerStackTransform.reset();
+
     if (path.isClone()) {
         snapshot.inputInfo.inputConfig |= InputConfig::CLONE;
         // Cloned layers shouldn't handle watch outside since their z order is not determined by
         // WM or the client.
         snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH);
+
+        // Compute the transform that maps the clone's display to the layer stack space of the
+        // cloned window.
+        const LayerSnapshot* clonedSnapshot = getSnapshot(path.getClonedFrom());
+        if (clonedSnapshot != nullptr) {
+            const auto& [clonedInputBounds, s] =
+                    getInputBounds(*clonedSnapshot, /*fillParentBounds=*/false);
+            ui::Transform inputToLayer;
+            inputToLayer.set(clonedInputBounds.left, clonedInputBounds.top);
+            const ui::Transform& layerToLayerStack = getInputTransform(*clonedSnapshot);
+            const auto& displayToInput = snapshot.inputInfo.transform;
+            snapshot.inputInfo.cloneLayerStackTransform =
+                    layerToLayerStack * inputToLayer * displayToInput;
+        }
     }
 }
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index ee9302b..591ebb2 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -107,6 +107,8 @@
     hdrMetadata.validTypes = 0;
     surfaceDamageRegion = Region::INVALID_REGION;
     cornerRadius = 0.0f;
+    clientDrawnCornerRadius = 0.0f;
+    clientDrawnShadowRadius = 0.0f;
     backgroundBlurRadius = 0;
     api = -1;
     hasColorTransform = false;
@@ -348,6 +350,16 @@
         requestedFrameRate.category = category;
         changes |= RequestedLayerState::Changes::FrameRate;
     }
+
+    if (clientState.what & layer_state_t::eClientDrawnCornerRadiusChanged) {
+        clientDrawnCornerRadius = clientState.clientDrawnCornerRadius;
+        changes |= RequestedLayerState::Changes::Geometry;
+    }
+
+    if (clientState.what & layer_state_t::eClientDrawnShadowsChanged) {
+        clientDrawnShadowRadius = clientState.clientDrawnShadowRadius;
+        changes |= RequestedLayerState::Changes::Geometry;
+    }
 }
 
 ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const {
@@ -624,6 +636,8 @@
     const uint64_t deniedChanges = layer_state_t::ePositionChanged | layer_state_t::eAlphaChanged |
             layer_state_t::eColorTransformChanged | layer_state_t::eBackgroundColorChanged |
             layer_state_t::eMatrixChanged | layer_state_t::eCornerRadiusChanged |
+            layer_state_t::eClientDrawnCornerRadiusChanged |
+            layer_state_t::eClientDrawnShadowsChanged |
             layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBufferTransformChanged |
             layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
             layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged |
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 7ddd7ba..dd861a7 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -23,7 +23,7 @@
 #include "Scheduler/LayerInfo.h"
 
 #include "LayerCreationArgs.h"
-#include "TransactionState.h"
+#include "QueuedTransactionState.h"
 
 namespace android::surfaceflinger::frontend {
 using namespace ftl::flag_operators;
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index a1e8213..5bf86e5 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -28,7 +28,7 @@
 
 namespace android::surfaceflinger::frontend {
 
-void TransactionHandler::queueTransaction(TransactionState&& state) {
+void TransactionHandler::queueTransaction(QueuedTransactionState&& state) {
     mLocklessTransactionQueue.push(std::move(state));
     mPendingTransactionCount.fetch_add(1);
     SFTRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
@@ -45,9 +45,9 @@
     }
 }
 
-std::vector<TransactionState> TransactionHandler::flushTransactions() {
+std::vector<QueuedTransactionState> TransactionHandler::flushTransactions() {
     // Collect transaction that are ready to be applied.
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     TransactionFlushState flushState;
     flushState.queueProcessTime = systemTime();
     // Transactions with a buffer pending on a barrier may be on a different applyToken
@@ -76,7 +76,7 @@
 }
 
 void TransactionHandler::applyUnsignaledBufferTransaction(
-        std::vector<TransactionState>& transactions, TransactionFlushState& flushState) {
+        std::vector<QueuedTransactionState>& transactions, TransactionFlushState& flushState) {
     if (!flushState.queueWithUnsignaledBuffer) {
         return;
     }
@@ -98,9 +98,9 @@
     }
 }
 
-void TransactionHandler::popTransactionFromPending(std::vector<TransactionState>& transactions,
-                                                   TransactionFlushState& flushState,
-                                                   std::queue<TransactionState>& queue) {
+void TransactionHandler::popTransactionFromPending(
+        std::vector<QueuedTransactionState>& transactions, TransactionFlushState& flushState,
+        std::queue<QueuedTransactionState>& queue) {
     auto& transaction = queue.front();
     // Transaction is ready move it from the pending queue.
     flushState.firstTransaction = false;
@@ -146,8 +146,8 @@
     return ready;
 }
 
-int TransactionHandler::flushPendingTransactionQueues(std::vector<TransactionState>& transactions,
-                                                      TransactionFlushState& flushState) {
+int TransactionHandler::flushPendingTransactionQueues(
+        std::vector<QueuedTransactionState>& transactions, TransactionFlushState& flushState) {
     int transactionsPendingBarrier = 0;
     auto it = mPendingTransactionQueues.begin();
     while (it != mPendingTransactionQueues.end()) {
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 00f6bce..e78dd88 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -22,7 +22,7 @@
 #include <vector>
 
 #include <LocklessQueue.h>
-#include <TransactionState.h>
+#include <QueuedTransactionState.h>
 #include <android-base/thread_annotations.h>
 #include <ftl/small_map.h>
 #include <ftl/small_vector.h>
@@ -35,7 +35,7 @@
 class TransactionHandler {
 public:
     struct TransactionFlushState {
-        TransactionState* transaction;
+        QueuedTransactionState* transaction;
         bool firstTransaction = true;
         nsecs_t queueProcessTime = 0;
         // Layer handles that have transactions with buffers that are ready to be applied.
@@ -61,9 +61,9 @@
     bool hasPendingTransactions();
     // Moves transactions from the lockless queue.
     void collectTransactions();
-    std::vector<TransactionState> flushTransactions();
+    std::vector<QueuedTransactionState> flushTransactions();
     void addTransactionReadyFilter(TransactionFilter&&);
-    void queueTransaction(TransactionState&&);
+    void queueTransaction(QueuedTransactionState&&);
 
     struct StalledTransactionInfo {
         pid_t pid;
@@ -81,14 +81,15 @@
     // For unit tests
     friend class ::android::TestableSurfaceFlinger;
 
-    int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
-    void applyUnsignaledBufferTransaction(std::vector<TransactionState>&, TransactionFlushState&);
-    void popTransactionFromPending(std::vector<TransactionState>&, TransactionFlushState&,
-                                   std::queue<TransactionState>&);
+    int flushPendingTransactionQueues(std::vector<QueuedTransactionState>&, TransactionFlushState&);
+    void applyUnsignaledBufferTransaction(std::vector<QueuedTransactionState>&,
+                                          TransactionFlushState&);
+    void popTransactionFromPending(std::vector<QueuedTransactionState>&, TransactionFlushState&,
+                                   std::queue<QueuedTransactionState>&);
     TransactionReadiness applyFilters(TransactionFlushState&);
-    std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
+    std::unordered_map<sp<IBinder>, std::queue<QueuedTransactionState>, IListenerHash>
             mPendingTransactionQueues;
-    LocklessQueue<TransactionState> mLocklessTransactionQueue;
+    LocklessQueue<QueuedTransactionState> mLocklessTransactionQueue;
     std::atomic<size_t> mPendingTransactionCount = 0;
     ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters;
 
diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h
index 4af27ab..f7dfeb8 100644
--- a/services/surfaceflinger/FrontEnd/Update.h
+++ b/services/surfaceflinger/FrontEnd/Update.h
@@ -19,15 +19,15 @@
 #include <gui/DisplayInfo.h>
 
 #include "FrontEnd/LayerCreationArgs.h"
+#include "QueuedTransactionState.h"
 #include "RequestedLayerState.h"
-#include "TransactionState.h"
 
 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<QueuedTransactionState> transactions;
     std::vector<sp<Layer>> legacyLayers;
     std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
     std::vector<LayerCreationArgs> layerCreationArgs;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c234a75..6af0f59 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -516,11 +516,6 @@
 
     bool mGetHandleCalled = false;
 
-    // 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.
-    float mEffectiveShadowRadius = 0.f;
-
     // Game mode for the layer. Set by WindowManagerShell and recorded by SurfaceFlingerStats.
     gui::GameMode mGameMode = gui::GameMode::Unsupported;
 
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index fea7671..725a782 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -173,7 +173,7 @@
     layerSettings.edgeExtensionEffect = mSnapshot->edgeExtensionEffect;
     // Record the name of the layer for debugging further down the stack.
     layerSettings.name = mSnapshot->name;
-    layerSettings.luts = mSnapshot->luts;
+    layerSettings.luts = mSnapshot->luts ? mSnapshot->luts : targetSettings.luts;
 
     if (hasEffect() && !hasBufferOrSidebandStream()) {
         prepareEffectsClientComposition(layerSettings, targetSettings);
@@ -191,6 +191,7 @@
     layerSettings.disableBlending = true;
     layerSettings.bufferId = 0;
     layerSettings.frameNumber = 0;
+    layerSettings.sequence = -1;
 
     // If layer is blacked out, force alpha to 1 so that we draw a black color layer.
     layerSettings.alpha = blackout ? 1.0f : 0.0f;
@@ -262,6 +263,7 @@
     layerSettings.source.buffer.maxLuminanceNits = maxLuminance;
     layerSettings.frameNumber = mSnapshot->frameNumber;
     layerSettings.bufferId = mSnapshot->externalTexture->getId();
+    layerSettings.sequence = mSnapshot->sequence;
 
     const bool useFiltering = targetSettings.needsFiltering ||
                               mSnapshot->geomLayerTransform.needsBilinearFiltering();
@@ -425,4 +427,14 @@
 LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() {
     return mReleaseFencePromiseStatus;
 }
+
+void LayerFE::setHwcCompositionType(
+        aidl::android::hardware::graphics::composer3::Composition type) {
+    mLastHwcCompositionType = type;
+}
+
+aidl::android::hardware::graphics::composer3::Composition LayerFE::getHwcCompositionType() const {
+    return mLastHwcCompositionType;
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 9483aeb..64ec278 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -59,6 +59,9 @@
     void setReleaseFence(const FenceResult& releaseFence) override;
     LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
     void onPictureProfileCommitted() override;
+    void setHwcCompositionType(aidl::android::hardware::graphics::composer3::Composition) override;
+    aidl::android::hardware::graphics::composer3::Composition getHwcCompositionType()
+            const override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
@@ -90,6 +93,8 @@
     std::string mName;
     std::promise<FenceResult> mReleaseFence;
     ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
+    aidl::android::hardware::graphics::composer3::Composition mLastHwcCompositionType =
+            aidl::android::hardware::graphics::composer3::Composition::INVALID;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp
index ff45272..cd7210c 100644
--- a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp
+++ b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp
@@ -29,7 +29,9 @@
 
 #include <android-base/properties.h>
 #include <android/binder_libbinder.h>
+#include <common/WorkloadTracer.h>
 #include <common/trace.h>
+#include <ftl/concat.h>
 #include <utils/Log.h>
 #include <utils/Mutex.h>
 
@@ -44,6 +46,7 @@
 
 namespace android::adpf::impl {
 
+using namespace android::ftl::flag_operators;
 using aidl::android::hardware::common::fmq::SynchronizedReadWrite;
 using android::hardware::EventFlag;
 
@@ -62,6 +65,8 @@
     }
 }
 
+static constexpr ftl::Flags<Workload> TRIGGER_LOAD_CHANGE_HINTS = Workload::EFFECTS |
+        Workload::VISIBLE_REGION | Workload::DISPLAY_CHANGES | Workload::SCREENSHOT;
 } // namespace
 
 PowerAdvisor::PowerAdvisor(std::function<void()>&& sfDisableExpensiveFn,
@@ -756,4 +761,58 @@
     return *mPowerHal;
 }
 
+void PowerAdvisor::setQueuedWorkload(ftl::Flags<Workload> queued) {
+    queued &= TRIGGER_LOAD_CHANGE_HINTS;
+    if (!(queued).get()) return;
+    uint32_t previousQueuedWorkload = mQueuedWorkload.fetch_or(queued.get());
+
+    uint32_t newHints = (previousQueuedWorkload ^ queued.get()) & queued.get();
+    if (newHints) {
+        SFTRACE_INSTANT_FOR_TRACK(WorkloadTracer::TRACK_NAME,
+                                  ftl::Concat("QueuedWorkload: ",
+                                              ftl::truncated<20>(ftl::Flags<Workload>(newHints)
+                                                                         .string()
+                                                                         .c_str()))
+                                          .c_str());
+    }
+    if (!previousQueuedWorkload) {
+        // TODO(b/385028458) maybe load up hint if close to wake up
+    }
+}
+
+void PowerAdvisor::setScreenshotWorkload() {
+    mCommittedWorkload |= Workload::SCREENSHOT;
+}
+
+void PowerAdvisor::setCommittedWorkload(ftl::Flags<Workload> workload) {
+    workload &= TRIGGER_LOAD_CHANGE_HINTS;
+    uint32_t queued = mQueuedWorkload.exchange(0);
+    mCommittedWorkload |= workload;
+
+    bool cancelLoadupHint = queued && !mCommittedWorkload.get();
+    if (cancelLoadupHint) {
+        SFTRACE_INSTANT_FOR_TRACK(WorkloadTracer::TRACK_NAME,
+                                  ftl::Concat("UncommittedQueuedWorkload: ",
+                                              ftl::truncated<20>(ftl::Flags<Workload>(queued)
+                                                                         .string()
+                                                                         .c_str()))
+                                          .c_str());
+        // TODO(b/385028458) cancel load up hint
+    }
+
+    bool increasedWorkload = queued == 0 && mCommittedWorkload.get() != 0;
+    if (increasedWorkload) {
+        SFTRACE_INSTANT_FOR_TRACK(WorkloadTracer::TRACK_NAME,
+                                  ftl::Concat("CommittedWorkload: ",
+                                              ftl::truncated<20>(mCommittedWorkload.string()))
+                                          .c_str());
+
+        // TODO(b/385028458) load up hint
+    }
+}
+
+void PowerAdvisor::setCompositedWorkload(ftl::Flags<Workload> composited) {
+    composited &= TRIGGER_LOAD_CHANGE_HINTS;
+    mCommittedWorkload = composited;
+}
 } // namespace android::adpf::impl
diff --git a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h
index 43fc210..540a9df 100644
--- a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h
+++ b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h
@@ -32,9 +32,12 @@
 #include <fmq/AidlMessageQueue.h>
 #pragma clang diagnostic pop
 
+#include <common/trace.h>
+#include <ftl/flags.h>
 #include <scheduler/Time.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
+#include "Workload.h"
 
 #include "SessionManager.h"
 
@@ -109,6 +112,26 @@
     // Get the session manager, if it exists
     virtual std::shared_ptr<SessionManager> getSessionManager() = 0;
 
+    // --- Track per frame workloads to use for load up hint heuristics
+    // Track queued workload from transactions as they are queued from the binder thread.
+    // The workload is accumulated and reset on frame commit. The queued workload may be
+    // relevant for the next frame so can be used as an early load up hint. Note this is
+    // only a hint because the transaction can remain in the queue and not be applied on
+    // the next frame.
+    virtual void setQueuedWorkload(ftl::Flags<Workload> workload) = 0;
+    // Track additional workload dur to a screenshot request for load up hint heuristics. This
+    // would indicate an immediate increase in GPU workload.
+    virtual void setScreenshotWorkload() = 0;
+    // Track committed workload from transactions that are applied on the main thread.
+    // This workload is determined from the applied transactions. This can provide a high
+    // confidence that the CPU and or GPU workload will increase immediately.
+    virtual void setCommittedWorkload(ftl::Flags<Workload> workload) = 0;
+    // Update committed workload with the actual workload from post composition. This is
+    // used to update the baseline workload so we can detect increases in workloads on the
+    // next commit. We use composite instead of commit to update the baseline to account
+    // for optimizations like caching which may reduce the workload.
+    virtual void setCompositedWorkload(ftl::Flags<Workload> workload) = 0;
+
     // --- The following methods may run on threads besides SF main ---
     // Send a hint about an upcoming increase in the CPU workload
     virtual void notifyCpuLoadUp() = 0;
@@ -158,6 +181,11 @@
     void setTotalFrameTargetWorkDuration(Duration targetDuration) override;
     std::shared_ptr<SessionManager> getSessionManager() override;
 
+    void setQueuedWorkload(ftl::Flags<Workload> workload) override;
+    void setScreenshotWorkload() override;
+    void setCommittedWorkload(ftl::Flags<Workload> workload) override;
+    void setCompositedWorkload(ftl::Flags<Workload> workload) override;
+
     // --- The following methods may run on threads besides SF main ---
     void notifyCpuLoadUp() override;
     void notifyDisplayUpdateImminentAndCpuReset() override;
@@ -332,6 +360,11 @@
     static constexpr const Duration kFenceWaitStartDelayValidated{150us};
     static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us};
 
+    // Track queued and committed workloads per frame. Queued workload is atomic because it's
+    // updated on both binder and the main thread.
+    std::atomic<uint32_t> mQueuedWorkload;
+    ftl::Flags<Workload> mCommittedWorkload;
+
     void sendHintSessionHint(aidl::android::hardware::power::SessionHint hint);
 
     template <aidl::android::hardware::power::ChannelMessage::ChannelMessageContents::Tag T,
diff --git a/services/surfaceflinger/PowerAdvisor/Workload.h b/services/surfaceflinger/PowerAdvisor/Workload.h
new file mode 100644
index 0000000..7002357
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/Workload.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 <ftl/flags.h>
+#include <stdint.h>
+
+namespace android::adpf {
+// Additional composition workload that can increase cpu load.
+enum class Workload : uint32_t {
+    NONE = 0,
+    // Layer effects like blur and shadows which forces client composition
+    EFFECTS = 1 << 0,
+
+    // Geometry changes which requires HWC to validate and share composition strategy
+    VISIBLE_REGION = 1 << 1,
+
+    // Diplay changes which can cause geometry changes
+    DISPLAY_CHANGES = 1 << 2,
+
+    // Changes in sf duration which can shorten the deadline for sf to composite the frame
+    WAKEUP = 1 << 3,
+
+    // Increases in refresh rates can cause the deadline for sf to composite to be shorter
+    REFRESH_RATE_INCREASE = 1 << 4,
+
+    // Screenshot requests increase both the cpu and gpu workload
+    SCREENSHOT = 1 << 5
+};
+} // namespace android::adpf
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/QueuedTransactionState.h
similarity index 84%
rename from services/surfaceflinger/TransactionState.h
rename to services/surfaceflinger/QueuedTransactionState.h
index e5d6481..86683da 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/QueuedTransactionState.h
@@ -16,14 +16,14 @@
 
 #pragma once
 
-#include <condition_variable>
 #include <memory>
-#include <mutex>
 #include <vector>
 #include "FrontEnd/LayerCreationArgs.h"
 #include "renderengine/ExternalTexture.h"
 
+#include <PowerAdvisor/Workload.h>
 #include <common/FlagManager.h>
+#include <ftl/flags.h>
 #include <gui/LayerState.h>
 #include <system/window.h>
 
@@ -47,18 +47,20 @@
     uint32_t touchCropId = UNASSIGNED_LAYER_ID;
 };
 
-struct TransactionState {
-    TransactionState() = default;
+struct QueuedTransactionState {
+    QueuedTransactionState() = default;
 
-    TransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                     std::vector<ResolvedComposerState>& composerStates,
-                     const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
-                     const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
-                     int64_t desiredPresentTime, bool isAutoTimestamp,
-                     std::vector<uint64_t> uncacheBufferIds, int64_t postTime,
-                     bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
-                     int originPid, int originUid, uint64_t transactionId,
-                     std::vector<uint64_t> mergedTransactionIds)
+    QueuedTransactionState(const FrameTimelineInfo& frameTimelineInfo,
+                           std::vector<ResolvedComposerState>& composerStates,
+                           const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
+                           const sp<IBinder>& applyToken,
+                           const InputWindowCommands& inputWindowCommands,
+                           int64_t desiredPresentTime, bool isAutoTimestamp,
+                           std::vector<uint64_t> uncacheBufferIds, int64_t postTime,
+                           bool hasListenerCallbacks,
+                           std::vector<ListenerCallbacks> listenerCallbacks, int originPid,
+                           int originUid, uint64_t transactionId,
+                           std::vector<uint64_t> mergedTransactionIds)
           : frameTimelineInfo(frameTimelineInfo),
             states(std::move(composerStates)),
             displays(displayStates),
@@ -148,6 +150,7 @@
     uint64_t id;
     bool sentFenceTimeoutWarning = false;
     std::vector<uint64_t> mergedTransactionIds;
+    ftl::Flags<adpf::Workload> workloadHint;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 6e2b943..8c22de1 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -504,7 +504,7 @@
             return FrameRateCompatibility::Exact;
         case ANATIVEWINDOW_FRAME_RATE_MIN:
             return FrameRateCompatibility::Min;
-        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE:
+        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST:
             return FrameRateCompatibility::Gte;
         case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
             return FrameRateCompatibility::NoVote;
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index 41a9a1b..5f71b88 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -16,11 +16,13 @@
 
 #include "ScreenCaptureOutput.h"
 #include "ScreenCaptureRenderSurface.h"
+#include "common/include/common/FlagManager.h"
 #include "ui/Rotation.h"
 
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/DisplayColorProfileCreationArgs.h>
 #include <compositionengine/impl/DisplayColorProfile.h>
+#include <ui/HdrRenderTypeUtils.h>
 #include <ui/Rotation.h>
 
 namespace android {
@@ -104,14 +106,84 @@
     return clientCompositionDisplay;
 }
 
+std::unordered_map<int32_t, aidl::android::hardware::graphics::composer3::Luts>
+ScreenCaptureOutput::generateLuts() {
+    std::unordered_map<int32_t, aidl::android::hardware::graphics::composer3::Luts> lutsMapper;
+    if (FlagManager::getInstance().luts_api()) {
+        std::vector<sp<GraphicBuffer>> buffers;
+        std::vector<int32_t> layerIds;
+
+        for (const auto* layer : getOutputLayersOrderedByZ()) {
+            const auto& layerState = layer->getState();
+            const auto* layerFEState = layer->getLayerFE().getCompositionState();
+            auto pixelFormat = layerFEState->buffer
+                    ? std::make_optional(
+                              static_cast<ui::PixelFormat>(layerFEState->buffer->getPixelFormat()))
+                    : std::nullopt;
+            const auto hdrType = getHdrRenderType(layerState.dataspace, pixelFormat,
+                                                  layerFEState->desiredHdrSdrRatio);
+            if (layerFEState->buffer && !layerFEState->luts &&
+                hdrType == HdrRenderType::GENERIC_HDR) {
+                buffers.push_back(layerFEState->buffer);
+                layerIds.push_back(layer->getLayerFE().getSequence());
+            }
+        }
+
+        std::vector<aidl::android::hardware::graphics::composer3::Luts> luts;
+        if (auto displayDevice = mRenderArea.getDisplayDevice()) {
+            const auto id = PhysicalDisplayId::tryCast(displayDevice->getId());
+            if (id) {
+                auto& hwc = getCompositionEngine().getHwComposer();
+                hwc.getLuts(*id, buffers, &luts);
+            }
+        }
+
+        if (buffers.size() == luts.size()) {
+            for (size_t i = 0; i < luts.size(); i++) {
+                lutsMapper[layerIds[i]] = std::move(luts[i]);
+            }
+        }
+    }
+    return lutsMapper;
+}
+
 std::vector<compositionengine::LayerFE::LayerSettings>
 ScreenCaptureOutput::generateClientCompositionRequests(
         bool supportsProtectedContent, ui::Dataspace outputDataspace,
         std::vector<compositionengine::LayerFE*>& outLayerFEs) {
+    // This map maps the layer unique id to a Lut
+    std::unordered_map<int32_t, aidl::android::hardware::graphics::composer3::Luts> lutsMapper =
+            generateLuts();
+
     auto clientCompositionLayers = compositionengine::impl::Output::
             generateClientCompositionRequests(supportsProtectedContent, outputDataspace,
                                               outLayerFEs);
 
+    for (auto& layer : clientCompositionLayers) {
+        if (lutsMapper.find(layer.sequence) != lutsMapper.end()) {
+            auto& aidlLuts = lutsMapper[layer.sequence];
+            if (aidlLuts.pfd.get() >= 0 && aidlLuts.offsets) {
+                std::vector<int32_t> offsets = *aidlLuts.offsets;
+                std::vector<int32_t> dimensions;
+                dimensions.reserve(offsets.size());
+                std::vector<int32_t> sizes;
+                sizes.reserve(offsets.size());
+                std::vector<int32_t> keys;
+                keys.reserve(offsets.size());
+                for (size_t j = 0; j < offsets.size(); j++) {
+                    dimensions.emplace_back(
+                            static_cast<int32_t>(aidlLuts.lutProperties[j].dimension));
+                    sizes.emplace_back(aidlLuts.lutProperties[j].size);
+                    keys.emplace_back(
+                            static_cast<int32_t>(aidlLuts.lutProperties[j].samplingKeys[0]));
+                }
+                layer.luts = std::make_shared<gui::DisplayLuts>(base::unique_fd(
+                                                                        aidlLuts.pfd.dup().get()),
+                                                                offsets, dimensions, sizes, keys);
+            }
+        }
+    }
+
     if (mRegionSampling) {
         for (auto& layer : clientCompositionLayers) {
             layer.backgroundBlurRadius = 0;
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
index c233ead..444a28f 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.h
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -20,6 +20,7 @@
 #include <compositionengine/RenderSurface.h>
 #include <compositionengine/impl/Output.h>
 #include <ui/Rect.h>
+#include <unordered_map>
 
 #include "RenderArea.h"
 
@@ -65,6 +66,7 @@
             const std::shared_ptr<renderengine::ExternalTexture>& buffer) const override;
 
 private:
+    std::unordered_map<int32_t, aidl::android::hardware::graphics::composer3::Luts> generateLuts();
     const RenderArea& mRenderArea;
     const compositionengine::Output::ColorProfile& mColorProfile;
     const bool mRegionSampling;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e05c5bd9..d6225e2 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -44,6 +44,7 @@
 #include <com_android_graphics_libgui_flags.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include <common/FlagManager.h>
+#include <common/WorkloadTracer.h>
 #include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
@@ -156,6 +157,7 @@
 #include "MutexUtils.h"
 #include "NativeWindowSurface.h"
 #include "PowerAdvisor/PowerAdvisor.h"
+#include "PowerAdvisor/Workload.h"
 #include "RegionSamplingThread.h"
 #include "RenderAreaBuilder.h"
 #include "Scheduler/EventThread.h"
@@ -879,6 +881,8 @@
         return renderengine::RenderEngine::BlurAlgorithm::GAUSSIAN;
     } else if (algorithm == "kawase2") {
         return renderengine::RenderEngine::BlurAlgorithm::KAWASE_DUAL_FILTER;
+    } else if (algorithm == "kawase") {
+        return renderengine::RenderEngine::BlurAlgorithm::KAWASE;
     } else {
         if (FlagManager::getInstance().window_blur_kawase2()) {
             return renderengine::RenderEngine::BlurAlgorithm::KAWASE_DUAL_FILTER;
@@ -920,7 +924,8 @@
 
     mCompositionEngine->setTimeStats(mTimeStats);
 
-    mCompositionEngine->setHwComposer(getFactory().createHWComposer(mHwcServiceName));
+    mHWComposer = getFactory().createHWComposer(mHwcServiceName);
+    mCompositionEngine->setHwComposer(mHWComposer.get());
     auto& composer = mCompositionEngine->getHwComposer();
     composer.setCallback(*this);
     mDisplayModeController.setHwComposer(&composer);
@@ -2457,6 +2462,7 @@
                                           bool flushTransactions, bool& outTransactionsAreEmpty) {
     using Changes = frontend::RequestedLayerState::Changes;
     SFTRACE_CALL();
+    SFTRACE_NAME_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Transaction Handling");
     frontend::Update update;
     if (flushTransactions) {
         SFTRACE_NAME("TransactionHandler:flushTransactions");
@@ -2483,8 +2489,20 @@
             mDestroyedHandles.clear();
         }
 
+        size_t addedLayers = update.newLayers.size();
         mLayerLifecycleManager.addLayers(std::move(update.newLayers));
         update.transactions = mTransactionHandler.flushTransactions();
+        ftl::Flags<adpf::Workload> committedWorkload;
+        for (auto& transaction : update.transactions) {
+            committedWorkload |= transaction.workloadHint;
+        }
+        SFTRACE_INSTANT_FOR_TRACK(WorkloadTracer::TRACK_NAME,
+                                  ftl::Concat("Layers: +", addedLayers, " -",
+                                              update.destroyedHandles.size(),
+                                              " txns:", update.transactions.size())
+                                          .c_str());
+
+        mPowerAdvisor->setCommittedWorkload(committedWorkload);
         if (mTransactionTracing) {
             mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId), frameTimeNs,
                                                           update, mFrontEndDisplayInfos,
@@ -2685,7 +2703,7 @@
             return false;
         }
     }
-
+    SFTRACE_NAME_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Commit");
     const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
 
     // Save this once per commit + composite to ensure consistency
@@ -2759,6 +2777,7 @@
     // Hold mStateLock as chooseRefreshRateForContent promotes wp<Layer> to sp<Layer>
     // and may eventually call to ~Layer() if it holds the last reference
     {
+        SFTRACE_NAME_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Refresh Rate Selection");
         bool updateAttachedChoreographer = mUpdateAttachedChoreographer;
         mUpdateAttachedChoreographer = false;
 
@@ -2785,6 +2804,8 @@
 
 CompositeResultsPerDisplay SurfaceFlinger::composite(
         PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters& frameTargeters) {
+    SFTRACE_ASYNC_FOR_TRACK_BEGIN(WorkloadTracer::TRACK_NAME, "Composition",
+                                  WorkloadTracer::COMPOSITION_TRACE_COOKIE);
     const scheduler::FrameTarget& pacesetterTarget =
             frameTargeters.get(pacesetterId)->get()->target();
 
@@ -2947,10 +2968,34 @@
     }
 
     mCompositionEngine->present(refreshArgs);
-    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+    ftl::Flags<adpf::Workload> compositedWorkload;
+    if (refreshArgs.updatingGeometryThisFrame || refreshArgs.updatingOutputGeometryThisFrame) {
+        compositedWorkload |= adpf::Workload::VISIBLE_REGION;
+    }
+    if (mFrontEndDisplayInfosChanged) {
+        compositedWorkload |= adpf::Workload::DISPLAY_CHANGES;
+        SFTRACE_INSTANT_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Display Changes");
+    }
 
+    int index = 0;
+    std::array<char, WorkloadTracer::COMPOSITION_SUMMARY_SIZE> compositionSummary = {0};
+    auto lastLayerStack = ui::INVALID_LAYER_STACK;
     for (auto& [layer, layerFE] : layers) {
         CompositionResult compositionResult{layerFE->stealCompositionResult()};
+        if (index < compositionSummary.size()) {
+            if (lastLayerStack != ui::INVALID_LAYER_STACK &&
+                lastLayerStack != layerFE->mSnapshot->outputFilter.layerStack) {
+                // add a space to separate displays
+                compositionSummary[index++] = ' ';
+            }
+            lastLayerStack = layerFE->mSnapshot->outputFilter.layerStack;
+            compositionSummary[index++] = layerFE->mSnapshot->classifyCompositionForDebug(
+                    layerFE->getHwcCompositionType());
+            if (layerFE->mSnapshot->hasEffect()) {
+                compositedWorkload |= adpf::Workload::EFFECTS;
+            }
+        }
+
         if (compositionResult.lastClientCompositionFence) {
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
         }
@@ -2959,6 +3004,20 @@
         }
     }
 
+    // Concisely describe the layers composited this frame using single chars. GPU composited layers
+    // are uppercase, DPU composited are lowercase. Special chars denote effects (blur, shadow,
+    // etc.). This provides a snapshot of the compositing workload.
+    SFTRACE_INSTANT_FOR_TRACK(WorkloadTracer::TRACK_NAME,
+                              ftl::Concat("Layers: ", layers.size(), " ",
+                                          ftl::truncated<WorkloadTracer::COMPOSITION_SUMMARY_SIZE>(
+                                                  compositionSummary.data()))
+                                      .c_str());
+
+    mPowerAdvisor->setCompositedWorkload(compositedWorkload);
+    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+    SFTRACE_ASYNC_FOR_TRACK_END(WorkloadTracer::TRACK_NAME,
+                                WorkloadTracer::COMPOSITION_TRACE_COOKIE);
+    SFTRACE_NAME_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Post Composition");
     SFTRACE_NAME("postComposition");
     mTimeStats->recordFrameDuration(pacesetterTarget.frameBeginTime().ns(), systemTime());
 
@@ -3212,12 +3271,12 @@
 
     const auto schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime);
-    const Period vsyncPeriod = schedule->period();
+    const Fps renderRate = pacesetterDisplay->refreshRateSelector().getActiveMode().fps;
     const nsecs_t vsyncPhase =
             mScheduler->getVsyncConfiguration().getCurrentConfigs().late.sfOffset;
 
-    const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase,
-                                            presentLatency.ns());
+    const CompositorTiming compositorTiming(vsyncDeadline.ns(), renderRate.getPeriodNsecs(),
+                                            vsyncPhase, presentLatency.ns());
 
     ui::DisplayMap<ui::LayerStack, const DisplayDevice*> layerStackToDisplay;
     {
@@ -4774,16 +4833,16 @@
 // For tests only
 bool SurfaceFlinger::flushTransactionQueues() {
     mTransactionHandler.collectTransactions();
-    std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions();
+    std::vector<QueuedTransactionState> transactions = mTransactionHandler.flushTransactions();
     return applyTransactions(transactions);
 }
 
-bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions) {
+bool SurfaceFlinger::applyTransactions(std::vector<QueuedTransactionState>& transactions) {
     Mutex::Autolock lock(mStateLock);
     return applyTransactionsLocked(transactions);
 }
 
-bool SurfaceFlinger::applyTransactionsLocked(std::vector<TransactionState>& transactions) {
+bool SurfaceFlinger::applyTransactionsLocked(std::vector<QueuedTransactionState>& transactions) {
     bool needsTraversal = false;
     // Now apply all transactions.
     for (auto& transaction : transactions) {
@@ -4873,8 +4932,15 @@
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
     uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid);
+    ftl::Flags<adpf::Workload> queuedWorkload;
     for (auto& composerState : states) {
         composerState.state.sanitize(permissions);
+        if (composerState.state.what & layer_state_t::COMPOSITION_EFFECTS) {
+            queuedWorkload |= adpf::Workload::EFFECTS;
+        }
+        if (composerState.state.what & layer_state_t::VISIBLE_REGION_CHANGES) {
+            queuedWorkload |= adpf::Workload::VISIBLE_REGION;
+        }
     }
 
     for (DisplayState& display : displays) {
@@ -4897,6 +4963,10 @@
             flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);
         }
     }
+    if (flags & eEarlyWakeupStart) {
+        queuedWorkload |= adpf::Workload::WAKEUP;
+    }
+    mPowerAdvisor->setQueuedWorkload(queuedWorkload);
 
     const int64_t postTime = systemTime();
 
@@ -4944,22 +5014,23 @@
         }
     }
 
-    TransactionState state{frameTimelineInfo,
-                           resolvedStates,
-                           displays,
-                           flags,
-                           applyToken,
-                           std::move(inputWindowCommands),
-                           desiredPresentTime,
-                           isAutoTimestamp,
-                           std::move(uncacheBufferIds),
-                           postTime,
-                           hasListenerCallbacks,
-                           listenerCallbacks,
-                           originPid,
-                           originUid,
-                           transactionId,
-                           mergedTransactionIds};
+    QueuedTransactionState state{frameTimelineInfo,
+                                 resolvedStates,
+                                 displays,
+                                 flags,
+                                 applyToken,
+                                 std::move(inputWindowCommands),
+                                 desiredPresentTime,
+                                 isAutoTimestamp,
+                                 std::move(uncacheBufferIds),
+                                 postTime,
+                                 hasListenerCallbacks,
+                                 listenerCallbacks,
+                                 originPid,
+                                 originUid,
+                                 transactionId,
+                                 mergedTransactionIds};
+    state.workloadHint = queuedWorkload;
 
     if (mTransactionTracing) {
         mTransactionTracing->addQueuedTransaction(state);
@@ -5044,7 +5115,7 @@
 }
 
 bool SurfaceFlinger::applyAndCommitDisplayTransactionStatesLocked(
-        std::vector<TransactionState>& transactions) {
+        std::vector<QueuedTransactionState>& transactions) {
     bool needsTraversal = false;
     uint32_t transactionFlags = 0;
     for (auto& transaction : transactions) {
@@ -5438,7 +5509,7 @@
 }
 
 void SurfaceFlinger::initializeDisplays() {
-    TransactionState state;
+    QueuedTransactionState state;
     state.inputWindowCommands = mInputWindowCommands;
     const nsecs_t now = systemTime();
     state.desiredPresentTime = now;
@@ -5453,7 +5524,7 @@
         state.displays.push(DisplayState(display.token(), ui::LayerStack::fromValue(layerStack++)));
     }
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back(state);
 
     {
@@ -7393,6 +7464,8 @@
         std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     return mScheduler
             ->schedule([=, this, &renderAreaBuilder, &layers]() REQUIRES(kMainThreadContext) {
+                SFTRACE_NAME_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Screenshot");
+                mPowerAdvisor->setScreenshotWorkload();
                 SFTRACE_NAME("getSnapshotsFromMainThread");
                 layers = getLayerSnapshotsFn();
                 // Non-threaded RenderEngine eventually returns to the main thread a 2nd time
@@ -7575,7 +7648,7 @@
 
     if (hdrBuffer && gainmapBuffer) {
         ftl::SharedFuture<FenceResult> hdrRenderFuture =
-                renderScreenImpl(renderArea.get(), hdrBuffer, regionSampling, grayscale,
+                renderScreenImpl(std::move(renderArea), hdrBuffer, regionSampling, grayscale,
                                  isProtected, captureResults, displayState, layers);
         captureResults.buffer = buffer->getBuffer();
         captureResults.optionalGainMap = gainmapBuffer->getBuffer();
@@ -7599,7 +7672,7 @@
                         })
                         .share();
     } else {
-        renderFuture = renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale,
+        renderFuture = renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale,
                                         isProtected, captureResults, displayState, layers);
     }
 
@@ -7620,7 +7693,8 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        const RenderArea* renderArea, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+        std::unique_ptr<const RenderArea> renderArea,
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
         const std::optional<OutputCompositionState>& displayState,
         const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
@@ -7697,6 +7771,7 @@
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
         compositionEngine->setRenderEngine(mRenderEngine.get());
+        compositionEngine->setHwComposer(mHWComposer.get());
 
         std::vector<sp<compositionengine::LayerFE>> layerFEs;
         layerFEs.reserve(layers.size());
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index c85c084..a793d50 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -87,6 +87,7 @@
 #include "LayerVector.h"
 #include "MutexUtils.h"
 #include "PowerAdvisor/PowerAdvisor.h"
+#include "QueuedTransactionState.h"
 #include "Scheduler/ISchedulerCallback.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/Scheduler.h"
@@ -95,7 +96,6 @@
 #include "Tracing/LayerTracing.h"
 #include "Tracing/TransactionTracing.h"
 #include "TransactionCallbackInvoker.h"
-#include "TransactionState.h"
 #include "Utils/OnceFuture.h"
 
 #include <algorithm>
@@ -803,8 +803,9 @@
     // For test only
     bool flushTransactionQueues() REQUIRES(kMainThreadContext);
 
-    bool applyTransactions(std::vector<TransactionState>&) REQUIRES(kMainThreadContext);
-    bool applyAndCommitDisplayTransactionStatesLocked(std::vector<TransactionState>& transactions)
+    bool applyTransactions(std::vector<QueuedTransactionState>&) REQUIRES(kMainThreadContext);
+    bool applyAndCommitDisplayTransactionStatesLocked(
+            std::vector<QueuedTransactionState>& transactions)
             REQUIRES(kMainThreadContext, mStateLock);
 
     // Returns true if there is at least one transaction that needs to be flushed
@@ -833,7 +834,7 @@
 
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
     bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
-    bool applyTransactionsLocked(std::vector<TransactionState>& transactions)
+    bool applyTransactionsLocked(std::vector<QueuedTransactionState>& transactions)
             REQUIRES(mStateLock, kMainThreadContext);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
@@ -893,7 +894,8 @@
             const std::shared_ptr<renderengine::ExternalTexture>& gainmapBuffer = nullptr);
 
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            const RenderArea*, const std::shared_ptr<renderengine::ExternalTexture>&,
+            std::unique_ptr<const RenderArea> renderArea,
+            const std::shared_ptr<renderengine::ExternalTexture>&,
             bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&,
             const std::optional<OutputCompositionState>& displayState,
             const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
@@ -1367,6 +1369,7 @@
     std::atomic<int> mNumTrustedPresentationListeners = 0;
 
     std::unique_ptr<compositionengine::CompositionEngine> mCompositionEngine;
+    std::unique_ptr<HWComposer> mHWComposer;
 
     CompositionCoveragePerDisplay mCompositionCoverage;
 
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index f39a4d2..2676ca6 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -20,8 +20,8 @@
 
 #include "FrontEnd/LayerCreationArgs.h"
 #include "LayerProtoHelper.h"
+#include "QueuedTransactionState.h"
 #include "TransactionProtoParser.h"
-#include "TransactionState.h"
 #include "gui/LayerState.h"
 
 namespace android::surfaceflinger {
@@ -51,7 +51,8 @@
     ~FakeExternalTexture() = default;
 };
 
-perfetto::protos::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
+perfetto::protos::TransactionState TransactionProtoParser::toProto(
+        const QueuedTransactionState& t) {
     perfetto::protos::TransactionState proto;
     proto.set_pid(t.originPid);
     proto.set_uid(t.originUid);
@@ -300,9 +301,9 @@
     return proto;
 }
 
-TransactionState TransactionProtoParser::fromProto(
+QueuedTransactionState TransactionProtoParser::fromProto(
         const perfetto::protos::TransactionState& proto) {
-    TransactionState t;
+    QueuedTransactionState t;
     t.originPid = proto.pid();
     t.originUid = proto.uid();
     t.frameTimelineInfo.vsyncId = proto.vsync_id();
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h
index b3ab71c..a02e231 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.h
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h
@@ -21,7 +21,7 @@
 
 #include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
-#include "TransactionState.h"
+#include "QueuedTransactionState.h"
 
 namespace android::surfaceflinger {
 
@@ -44,14 +44,14 @@
     TransactionProtoParser(std::unique_ptr<FlingerDataMapper> provider)
           : mMapper(std::move(provider)) {}
 
-    perfetto::protos::TransactionState toProto(const TransactionState&);
+    perfetto::protos::TransactionState toProto(const QueuedTransactionState&);
     perfetto::protos::TransactionState toProto(
             const std::map<uint32_t /* layerId */, TracingLayerState>&);
     perfetto::protos::LayerCreationArgs toProto(const LayerCreationArgs& args);
     perfetto::protos::LayerState toProto(const ResolvedComposerState&);
     static perfetto::protos::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack);
 
-    TransactionState fromProto(const perfetto::protos::TransactionState&);
+    QueuedTransactionState fromProto(const perfetto::protos::TransactionState&);
     void mergeFromProto(const perfetto::protos::LayerState&, TracingLayerState& outState);
     void fromProto(const perfetto::protos::LayerCreationArgs&, LayerCreationArgs& outArgs);
     std::unique_ptr<FlingerDataMapper> mMapper;
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index bc9f809..1cd7517 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -166,7 +166,7 @@
     mBuffer.dump(result);
 }
 
-void TransactionTracing::addQueuedTransaction(const TransactionState& transaction) {
+void TransactionTracing::addQueuedTransaction(const QueuedTransactionState& transaction) {
     perfetto::protos::TransactionState* state =
             new perfetto::protos::TransactionState(mProtoParser.toProto(transaction));
     mTransactionQueue.push(state);
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 7a0fb5e..d784168 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -134,7 +134,7 @@
     // Flush event from perfetto data source
     void onFlush(Mode mode);
 
-    void addQueuedTransaction(const TransactionState&);
+    void addQueuedTransaction(const QueuedTransactionState&);
     void addCommittedTransactions(int64_t vsyncId, nsecs_t commitTime, frontend::Update& update,
                                   const frontend::DisplayInfos&, bool displayInfoChanged);
     status_t writeToFile(const std::string& filename = FILE_PATH);
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 1dba175..5cf4244 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -31,8 +31,8 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/RequestedLayerState.h"
 #include "LayerProtoHelper.h"
+#include "QueuedTransactionState.h"
 #include "Tracing/LayerTracing.h"
-#include "TransactionState.h"
 #include "cutils/properties.h"
 
 #include "LayerTraceGenerator.h"
@@ -95,11 +95,11 @@
             addedLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
         }
 
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.reserve((size_t)entry.transactions_size());
         for (int j = 0; j < entry.transactions_size(); j++) {
             // apply transactions
-            TransactionState transaction = parser.fromProto(entry.transactions(j));
+            QueuedTransactionState transaction = parser.fromProto(entry.transactions(j));
             for (auto& resolvedComposerState : transaction.states) {
                 if (resolvedComposerState.state.what & layer_state_t::eInputInfoChanged) {
                     if (!resolvedComposerState.state.windowInfoHandle->getInfo()->inputConfig.test(
diff --git a/services/surfaceflinger/common/include/common/WorkloadTracer.h b/services/surfaceflinger/common/include/common/WorkloadTracer.h
new file mode 100644
index 0000000..39b6fa1
--- /dev/null
+++ b/services/surfaceflinger/common/include/common/WorkloadTracer.h
@@ -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.
+ */
+
+#pragma once
+
+#include <ftl/flags.h>
+#include <stdint.h>
+namespace android::WorkloadTracer {
+
+static constexpr int32_t COMPOSITION_TRACE_COOKIE = 1;
+static constexpr int32_t POST_COMPOSITION_TRACE_COOKIE = 2;
+static constexpr size_t COMPOSITION_SUMMARY_SIZE = 20;
+static constexpr const char* TRACK_NAME = "CriticalWorkload";
+
+} // namespace android::WorkloadTracer
\ No newline at end of file
diff --git a/services/surfaceflinger/common/include/common/trace.h b/services/surfaceflinger/common/include/common/trace.h
index dc5716b..9a7e97f 100644
--- a/services/surfaceflinger/common/include/common/trace.h
+++ b/services/surfaceflinger/common/include/common/trace.h
@@ -65,6 +65,8 @@
 #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_NAME_FOR_TRACK(trackName, name) \
+    ::android::ScopedTraceForTrack PASTE(___tracer, __LINE__)(trackName, name)
 
 #define SFTRACE_FORMAT(fmt, ...) \
     ::android::ScopedTrace PASTE(___tracer, __LINE__)(fmt, ##__VA_ARGS__)
@@ -87,4 +89,21 @@
     inline ~ScopedTrace() { SFTRACE_END(); }
 };
 
+class ScopedTraceForTrack {
+public:
+    inline ScopedTraceForTrack(const char* trackName, const char* name)
+          : mCookie(getUniqueCookie()), mTrackName(trackName) {
+        SFTRACE_ASYNC_FOR_TRACK_BEGIN(mTrackName, name, mCookie);
+    }
+    inline ~ScopedTraceForTrack() { SFTRACE_ASYNC_FOR_TRACK_END(mTrackName, mCookie); }
+
+private:
+    static int32_t getUniqueCookie() {
+        static std::atomic<int32_t> sUniqueCookie = 1000;
+        return sUniqueCookie++;
+    }
+    int32_t mCookie;
+    const char* mTrackName;
+};
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index f247c9f..151611c 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -585,6 +585,83 @@
     }
 }
 
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetClientDrawnCornerRadius) {
+    sp<SurfaceControl> layer;
+    const uint8_t size = 64;
+    const uint8_t testArea = 4;
+    const float cornerRadius = 20.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", size, size));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(layer, Color::RED, size, size));
+
+    Transaction()
+            .setClientDrawnCornerRadius(layer, cornerRadius)
+            .setCornerRadius(layer, cornerRadius)
+            .setCrop(layer, Rect(size, size))
+            .apply();
+
+    {
+        const uint8_t bottom = size - 1;
+        const uint8_t right = size - 1;
+        auto shot = getScreenCapture();
+        // Solid corners
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::RED);
+        shot->expectColor(Rect(size - testArea, 0, right, testArea), Color::RED);
+        shot->expectColor(Rect(0, bottom - testArea, testArea, bottom), Color::RED);
+        shot->expectColor(Rect(size - testArea, bottom - testArea, right, bottom), Color::RED);
+        // Solid center
+        shot->expectColor(Rect(size / 2 - testArea / 2, size / 2 - testArea / 2,
+                               size / 2 + testArea / 2, size / 2 + testArea / 2),
+                          Color::RED);
+    }
+}
+
+// Test if ParentCornerRadiusTakesPrecedence if the parent's client drawn corner radius crop
+// is fully contained by the child corner radius crop.
+TEST_P(LayerTypeAndRenderTypeTransactionTest, ParentCornerRadiusPrecedenceClientDrawnCornerRadius) {
+    sp<SurfaceControl> parent;
+    sp<SurfaceControl> child;
+    const uint32_t size = 64;
+    const uint32_t parentSize = size * 3;
+    const Rect parentCrop(size, size, size, size);
+    const uint32_t testLength = 4;
+    const float cornerRadius = 20.0f;
+    ASSERT_NO_FATAL_FAILURE(parent = createLayer("parent", parentSize, parentSize));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(parent, Color::RED, parentSize, parentSize));
+    ASSERT_NO_FATAL_FAILURE(child = createLayer("child", size, size));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(child, Color::GREEN, size, size));
+
+    Transaction()
+            .setCornerRadius(parent, cornerRadius)
+            .setCrop(parent, parentCrop)
+            .setClientDrawnCornerRadius(parent, cornerRadius)
+            .reparent(child, parent)
+            .setPosition(child, size, size)
+            .apply(true);
+
+    {
+        const uint32_t top = size;
+        const uint32_t left = size;
+        const uint32_t bottom = size * 2;
+        const uint32_t right = size * 2;
+        auto shot = getScreenCapture();
+        // Corners are RED because parent's client drawn corner radius is actually 0
+        // and the child is fully within the parent's crop
+        // TL
+        shot->expectColor(Rect(left, top, testLength, testLength), Color::RED);
+        // TR
+        shot->expectColor(Rect(right - testLength, top, testLength, testLength), Color::RED);
+        // BL
+        shot->expectColor(Rect(left, bottom - testLength, testLength, testLength), Color::RED);
+        // BR
+        shot->expectColor(Rect(right - testLength, bottom - testLength, testLength, testLength),
+                          Color::RED);
+        // Solid center
+        shot->expectColor(Rect(parentSize / 2 - testLength, parentSize / 2 - testLength, testLength,
+                               testLength),
+                          Color::GREEN);
+    }
+}
+
 TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBackgroundBlurRadiusSimple) {
     if (!deviceSupportsBlurs()) GTEST_SKIP();
     if (!deviceUsesSkiaRenderEngine()) GTEST_SKIP();
diff --git a/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp b/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp
index 7641a45..0925118 100644
--- a/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp
+++ b/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp
@@ -50,7 +50,7 @@
     layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(1));
     lifecycleManager.addLayers(std::move(layers));
     lifecycleManager.commitChanges();
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     auto& transactionState = transactions.back().states.front();
@@ -74,7 +74,7 @@
     std::vector<std::unique_ptr<RequestedLayerState>> layers;
     layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(1));
     lifecycleManager.addLayers(std::move(layers));
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     auto& transactionState = transactions.back().states.front();
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
index 9794620..ee5d919 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -66,8 +66,8 @@
                                                                 /*mirror=*/UNASSIGNED_LAYER_ID));
     }
 
-    static std::vector<TransactionState> setZTransaction(uint32_t id, int32_t z) {
-        std::vector<TransactionState> transactions;
+    static std::vector<QueuedTransactionState> setZTransaction(uint32_t id, int32_t z) {
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -109,8 +109,9 @@
         mLifecycleManager.addLayers(std::move(layers));
     }
 
-    std::vector<TransactionState> reparentLayerTransaction(uint32_t id, uint32_t newParentId) {
-        std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> reparentLayerTransaction(uint32_t id,
+                                                                 uint32_t newParentId) {
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().parentId = newParentId;
@@ -124,8 +125,9 @@
         mLifecycleManager.applyTransactions(reparentLayerTransaction(id, newParentId));
     }
 
-    std::vector<TransactionState> relativeLayerTransaction(uint32_t id, uint32_t relativeParentId) {
-        std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> relativeLayerTransaction(uint32_t id,
+                                                                 uint32_t relativeParentId) {
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().relativeParentId = relativeParentId;
@@ -139,7 +141,7 @@
     }
 
     void removeRelativeZ(uint32_t id) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
@@ -148,7 +150,7 @@
     }
 
     void setPosition(uint32_t id, float x, float y) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::ePositionChanged;
@@ -167,7 +169,7 @@
     }
 
     void updateBackgroundColor(uint32_t id, half alpha) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
@@ -183,7 +185,7 @@
     }
 
     void setCrop(uint32_t id, const FloatRect& crop) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -196,7 +198,7 @@
     void setCrop(uint32_t id, const Rect& crop) { setCrop(id, crop.toFloatRect()); }
 
     void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -208,7 +210,7 @@
     }
 
     void setAlpha(uint32_t id, float alpha) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -219,7 +221,7 @@
     }
 
     void setAutoRefresh(uint32_t id, bool autoRefresh) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -236,7 +238,7 @@
     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;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::eColorChanged;
@@ -246,7 +248,7 @@
     }
 
     void setLayerStack(uint32_t id, int32_t layerStack) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -257,7 +259,7 @@
     }
 
     void setTouchableRegion(uint32_t id, Region region) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -272,7 +274,7 @@
     }
 
     void setInputInfo(uint32_t id, std::function<void(gui::WindowInfo&)> configureInput) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -291,7 +293,7 @@
 
     void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId,
                                 bool replaceTouchableRegionWithCrop) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -309,7 +311,7 @@
     }
 
     void setBackgroundBlurRadius(uint32_t id, uint32_t backgroundBlurRadius) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -320,7 +322,7 @@
     }
 
     void setFrameRateSelectionPriority(uint32_t id, int32_t priority) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -332,7 +334,7 @@
 
     void setFrameRate(uint32_t id, float frameRate, int8_t compatibility,
                       int8_t changeFrameRateStrategy) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -345,7 +347,7 @@
     }
 
     void setFrameRate(uint32_t id, Layer::FrameRate framerate) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -358,7 +360,7 @@
     }
 
     void setFrameRateCategory(uint32_t id, int8_t frameRateCategory) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -369,7 +371,7 @@
     }
 
     void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -381,7 +383,7 @@
     }
 
     void setDefaultFrameRateCompatibility(uint32_t id, int8_t defaultFrameRateCompatibility) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -394,7 +396,7 @@
     }
 
     void setRoundedCorners(uint32_t id, float radius) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -405,7 +407,7 @@
     }
 
     void setBuffer(uint32_t id, std::shared_ptr<renderengine::ExternalTexture> texture) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -438,7 +440,7 @@
     }
 
     void setBufferCrop(uint32_t id, const Rect& bufferCrop) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -449,7 +451,7 @@
     }
 
     void setDamageRegion(uint32_t id, const Region& damageRegion) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -460,7 +462,7 @@
     }
 
     void setDataspace(uint32_t id, ui::Dataspace dataspace) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -473,7 +475,7 @@
     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;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -483,8 +485,31 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setClientDrawnCornerRadius(uint32_t id, float clientDrawnCornerRadius) {
+        std::vector<QueuedTransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eClientDrawnCornerRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.clientDrawnCornerRadius = clientDrawnCornerRadius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setClientDrawnShadowRadius(uint32_t id, float clientDrawnShadowRadius) {
+        std::vector<QueuedTransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eClientDrawnShadowsChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.clientDrawnShadowRadius = clientDrawnShadowRadius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setShadowRadius(uint32_t id, float shadowRadius) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -495,7 +520,7 @@
     }
 
     void setTrustedOverlay(uint32_t id, gui::TrustedOverlay trustedOverlay) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -506,7 +531,7 @@
     }
 
     void setDropInputMode(uint32_t id, gui::DropInputMode dropInputMode) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
@@ -517,7 +542,7 @@
     }
 
     void setGameMode(uint32_t id, gui::GameMode gameMode) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
@@ -529,7 +554,7 @@
     }
 
     void setEdgeExtensionEffect(uint32_t id, int edge) {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 53a9062..f3d6dcc 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -584,7 +584,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -623,7 +623,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -662,7 +662,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
 
     EXPECT_EQ(1u, layerCount());
@@ -694,7 +694,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index c7cc21c..976cecb 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -21,7 +21,7 @@
 
 #include "FrontEnd/LayerLifecycleManager.h"
 #include "LayerHierarchyTest.h"
-#include "TransactionState.h"
+#include "QueuedTransactionState.h"
 
 using namespace android::surfaceflinger;
 
@@ -104,7 +104,7 @@
     EXPECT_FALSE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
 
     // apply transactions that do not affect the hierarchy
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.backgroundBlurRadius = 22;
@@ -297,7 +297,7 @@
     layers.emplace_back(rootLayer(1));
     lifecycleManager.addLayers(std::move(layers));
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0.5;
@@ -326,7 +326,7 @@
     layers.emplace_back(rootLayer(1));
     lifecycleManager.addLayers(std::move(layers));
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0.5;
@@ -360,7 +360,7 @@
     layers.emplace_back(rootLayer(1));
     lifecycleManager.addLayers(std::move(layers));
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.bgColor.a = 0.5;
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 4d322ef..ab8c733 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -28,6 +28,7 @@
 #include "ui/GraphicTypes.h"
 
 #include <com_android_graphics_libgui_flags.h>
+#include <cmath>
 
 #define UPDATE_AND_VERIFY(BUILDER, ...)                                    \
     ({                                                                     \
@@ -329,7 +330,7 @@
 }
 
 TEST_F(LayerSnapshotTest, UpdateMetadata) {
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
@@ -374,7 +375,7 @@
 TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) {
     hideLayer(1);
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
@@ -1425,6 +1426,93 @@
     EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
 }
 
+TEST_F(LayerSnapshotTest, setCornerRadius) {
+    static constexpr float RADIUS = 123.f;
+    setRoundedCorners(1, RADIUS);
+    setCrop(1, Rect{1000, 1000});
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, RADIUS);
+}
+
+TEST_F(LayerSnapshotTest, ignoreCornerRadius) {
+    static constexpr float RADIUS = 123.f;
+    setClientDrawnCornerRadius(1, RADIUS);
+    setRoundedCorners(1, RADIUS);
+    setCrop(1, Rect{1000, 1000});
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_TRUE(getSnapshot({.id = 1})->roundedCorner.hasClientDrawnRadius());
+    EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, 0.f);
+}
+
+TEST_F(LayerSnapshotTest, childInheritsParentScaledSettings) {
+    // ROOT
+    // ├── 1 (crop rect set to contain child layer)
+    // │   ├── 11
+    static constexpr float RADIUS = 123.f;
+
+    setRoundedCorners(1, RADIUS);
+    FloatRect parentCropRect(1, 1, 999, 999);
+    setCrop(1, parentCropRect);
+    // Rotate surface by 90
+    setMatrix(11, 0.f, -1.f, 1.f, 0.f);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    ui::Transform t = getSnapshot({.id = 11})->localTransform.inverse();
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.cropRect, t.transform(parentCropRect));
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.radius.x, RADIUS * t.getScaleX());
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.radius.y, RADIUS * t.getScaleY());
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.requestedRadius.x, RADIUS * t.getScaleX());
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.requestedRadius.y, RADIUS * t.getScaleY());
+}
+
+TEST_F(LayerSnapshotTest, childInheritsParentClientDrawnCornerRadius) {
+    // ROOT
+    // ├── 1 (crop rect set to contain child layers )
+    // │   ├── 11
+    // │   │   └── 111
+
+    static constexpr float RADIUS = 123.f;
+
+    setClientDrawnCornerRadius(1, RADIUS);
+    setRoundedCorners(1, RADIUS);
+    setCrop(1, Rect(1, 1, 999, 999));
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_TRUE(getSnapshot({.id = 1})->roundedCorner.hasClientDrawnRadius());
+    EXPECT_TRUE(getSnapshot({.id = 11})->roundedCorner.hasRoundedCorners());
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.radius.x, RADIUS);
+}
+
+TEST_F(LayerSnapshotTest, childIgnoreCornerRadiusOverridesParent) {
+    // ROOT
+    // ├── 1 (crop rect set to contain child layers )
+    // │   ├── 11
+    // │   │   └── 111
+
+    static constexpr float RADIUS = 123.f;
+
+    setRoundedCorners(1, RADIUS);
+    setCrop(1, Rect(1, 1, 999, 999));
+
+    setClientDrawnCornerRadius(11, RADIUS);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, RADIUS);
+    EXPECT_EQ(getSnapshot({.id = 11})->roundedCorner.radius.x, 0.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->roundedCorner.radius.x, RADIUS);
+}
+
+TEST_F(LayerSnapshotTest, ignoreShadows) {
+    static constexpr float SHADOW_RADIUS = 123.f;
+    setClientDrawnShadowRadius(1, SHADOW_RADIUS);
+    setShadowRadius(1, SHADOW_RADIUS);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->shadowSettings.length, 0.f);
+}
+
 TEST_F(LayerSnapshotTest, setShadowRadius) {
     static constexpr float SHADOW_RADIUS = 123.f;
     setShadowRadius(1, SHADOW_RADIUS);
@@ -1557,7 +1645,7 @@
     setColor(3, {-1._hf, -1._hf, -1._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
@@ -1586,7 +1674,7 @@
     setColor(3, {-1._hf, -1._hf, -1._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
@@ -2021,7 +2109,7 @@
     EXPECT_FALSE(getSnapshot(1)->contentDirty);
 }
 TEST_F(LayerSnapshotTest, shouldUpdatePictureProfileHandle) {
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.back().layerId = 1;
@@ -2040,7 +2128,7 @@
 
 TEST_F(LayerSnapshotTest, shouldUpdatePictureProfilePriorityFromAppContentPriority) {
     {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.back().layerId = 1;
@@ -2063,7 +2151,7 @@
                   2);
     }
     {
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
         transactions.back().states.back().layerId = 1;
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 5c25f34..d7f7bdb 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -39,6 +39,7 @@
 using namespace std::chrono_literals;
 using namespace testing;
 using namespace android::power;
+using namespace ftl::flag_operators;
 
 namespace android::adpf::impl {
 
@@ -54,6 +55,8 @@
     void setTimingTestingMode(bool testinMode);
     void allowReportActualToAcquireMutex();
     bool sessionExists();
+    ftl::Flags<Workload> getCommittedWorkload() const;
+    ftl::Flags<Workload> getQueuedWorkload() const;
     int64_t toNanos(Duration d);
 
     struct GpuTestConfig {
@@ -315,6 +318,14 @@
     return mPowerAdvisor->sTargetSafetyMargin;
 }
 
+ftl::Flags<Workload> PowerAdvisorTest::getCommittedWorkload() const {
+    return mPowerAdvisor->mCommittedWorkload;
+}
+
+ftl::Flags<Workload> PowerAdvisorTest::getQueuedWorkload() const {
+    return ftl::Flags<Workload>{mPowerAdvisor->mQueuedWorkload.load()};
+}
+
 namespace {
 
 TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) {
@@ -842,5 +853,32 @@
     ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
 }
 
+TEST_F(PowerAdvisorTest, trackQueuedWorkloads) {
+    mPowerAdvisor->setQueuedWorkload(ftl::Flags<Workload>());
+    ASSERT_EQ(getQueuedWorkload(), ftl::Flags<Workload>());
+
+    // verify workloads are queued
+    mPowerAdvisor->setQueuedWorkload(ftl::Flags<Workload>(Workload::VISIBLE_REGION));
+    ASSERT_EQ(getQueuedWorkload(), ftl::Flags<Workload>(Workload::VISIBLE_REGION));
+
+    mPowerAdvisor->setQueuedWorkload(ftl::Flags<Workload>(Workload::EFFECTS));
+    ASSERT_EQ(getQueuedWorkload(), Workload::VISIBLE_REGION | Workload::EFFECTS);
+
+    // verify queued workloads are cleared after commit
+    mPowerAdvisor->setCommittedWorkload(ftl::Flags<Workload>());
+    ASSERT_EQ(getQueuedWorkload(), ftl::Flags<Workload>());
+}
+
+TEST_F(PowerAdvisorTest, trackCommittedWorkloads) {
+    // verify queued workloads are cleared after commit
+    mPowerAdvisor->setCommittedWorkload(Workload::SCREENSHOT | Workload::VISIBLE_REGION);
+    ASSERT_EQ(getCommittedWorkload(), Workload::SCREENSHOT | Workload::VISIBLE_REGION);
+
+    // on composite, verify we update the committed workload so we track workload increases for the
+    // next frame accurately
+    mPowerAdvisor->setCompositedWorkload(Workload::VISIBLE_REGION | Workload::DISPLAY_CHANGES);
+    ASSERT_EQ(getCommittedWorkload(), Workload::VISIBLE_REGION | Workload::DISPLAY_CHANGES);
+}
+
 } // namespace
 } // namespace android::adpf::impl
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bd1382e..2353ef8 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -184,8 +184,8 @@
     }
 
     void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
-        mFlinger->mCompositionEngine->setHwComposer(
-                std::make_unique<impl::HWComposer>(std::move(composer)));
+        mFlinger->mHWComposer = std::make_unique<impl::HWComposer>(std::move(composer));
+        mFlinger->mCompositionEngine->setHwComposer(mFlinger->mHWComposer.get());
         mFlinger->mDisplayModeController.setHwComposer(
                 &mFlinger->mCompositionEngine->getHwComposer());
     }
@@ -473,7 +473,7 @@
         auto displayState = std::optional{display->getCompositionDisplay()->getState()};
         auto layers = getLayerSnapshotsFn();
 
-        return mFlinger->renderScreenImpl(renderArea.get(), buffer, regionSampling,
+        return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling,
                                           false /* grayscale */, false /* isProtected */,
                                           captureResults, displayState, layers);
     }
@@ -514,7 +514,7 @@
                                              mergedTransactionIds);
     }
 
-    auto setTransactionStateInternal(TransactionState& transaction) {
+    auto setTransactionStateInternal(QueuedTransactionState& transaction) {
         return FTL_FAKE_GUARD(kMainThreadContext,
                               mFlinger->mTransactionHandler.queueTransaction(
                                       std::move(transaction)));
@@ -771,7 +771,8 @@
         mutableCurrentState().displays.clear();
         mutableDrawingState().displays.clear();
         mFlinger->mScheduler.reset();
-        mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
+        mFlinger->mHWComposer = std::unique_ptr<HWComposer>();
+        mFlinger->mCompositionEngine->setHwComposer(mFlinger->mHWComposer.get());
         mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
         mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
         mFlinger->mTransactionTracing.reset();
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 1e8cd0a..69dfcc4 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -33,8 +33,8 @@
 #include <vector>
 
 #include "FrontEnd/TransactionHandler.h"
+#include "QueuedTransactionState.h"
 #include "TestableSurfaceFlinger.h"
-#include "TransactionState.h"
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 
@@ -84,7 +84,7 @@
         static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
     };
 
-    void checkEqual(TransactionInfo info, TransactionState state) {
+    void checkEqual(TransactionInfo info, QueuedTransactionState state) {
         EXPECT_EQ(0u, info.states.size());
         EXPECT_EQ(0u, state.states.size());
 
@@ -318,7 +318,7 @@
     auto applyToken2 = sp<BBinder>::make();
 
     // Transaction 1 has a buffer with an unfired fence. It should not be ready to be applied.
-    TransactionState transaction1;
+    QueuedTransactionState transaction1;
     transaction1.applyToken = applyToken1;
     transaction1.id = 42069;
     transaction1.states.emplace_back();
@@ -340,7 +340,7 @@
     transaction1.isAutoTimestamp = true;
 
     // Transaction 2 should be ready to be applied.
-    TransactionState transaction2;
+    QueuedTransactionState transaction2;
     transaction2.applyToken = applyToken2;
     transaction2.id = 2;
     transaction2.isAutoTimestamp = true;
@@ -446,15 +446,15 @@
                 resolvedStates.emplace_back(resolvedState);
             }
 
-            TransactionState transactionState(transaction.frameTimelineInfo, resolvedStates,
-                                              transaction.displays, transaction.flags,
-                                              transaction.applyToken,
-                                              transaction.inputWindowCommands,
-                                              transaction.desiredPresentTime,
-                                              transaction.isAutoTimestamp, {}, systemTime(),
-                                              mHasListenerCallbacks, mCallbacks, getpid(),
-                                              static_cast<int>(getuid()), transaction.id,
-                                              transaction.mergedTransactionIds);
+            QueuedTransactionState transactionState(transaction.frameTimelineInfo, resolvedStates,
+                                                    transaction.displays, transaction.flags,
+                                                    transaction.applyToken,
+                                                    transaction.inputWindowCommands,
+                                                    transaction.desiredPresentTime,
+                                                    transaction.isAutoTimestamp, {}, systemTime(),
+                                                    mHasListenerCallbacks, mCallbacks, getpid(),
+                                                    static_cast<int>(getuid()), transaction.id,
+                                                    transaction.mergedTransactionIds);
             mFlinger.setTransactionStateInternal(transactionState);
         }
         mFlinger.flushTransactionQueues();
@@ -955,12 +955,12 @@
 
 TEST(TransactionHandlerTest, QueueTransaction) {
     TransactionHandler handler;
-    TransactionState transaction;
+    QueuedTransactionState transaction;
     transaction.applyToken = sp<BBinder>::make();
     transaction.id = 42;
     handler.queueTransaction(std::move(transaction));
     handler.collectTransactions();
-    std::vector<TransactionState> transactionsReadyToBeApplied = handler.flushTransactions();
+    std::vector<QueuedTransactionState> transactionsReadyToBeApplied = handler.flushTransactions();
 
     EXPECT_EQ(transactionsReadyToBeApplied.size(), 1u);
     EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u);
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index af02330..d3eec5c 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -30,7 +30,7 @@
 
 TEST(TransactionProtoParserTest, parse) {
     const sp<IBinder> displayHandle = sp<BBinder>::make();
-    TransactionState t1;
+    QueuedTransactionState t1;
     t1.originPid = 1;
     t1.originUid = 2;
     t1.frameTimelineInfo.vsyncId = 3;
@@ -86,7 +86,7 @@
     TransactionProtoParser parser(std::make_unique<TestMapper>(displayHandle));
 
     perfetto::protos::TransactionState proto = parser.toProto(t1);
-    TransactionState t2 = parser.fromProto(proto);
+    QueuedTransactionState t2 = parser.fromProto(proto);
 
     ASSERT_EQ(t1.originPid, t2.originPid);
     ASSERT_EQ(t1.originUid, t2.originUid);
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index f8f08c7..036d8c4 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -49,19 +49,19 @@
 
     void queueAndCommitTransaction(int64_t vsyncId) {
         frontend::Update update;
-        TransactionState transaction;
+        QueuedTransactionState transaction;
         transaction.id = static_cast<uint64_t>(vsyncId * 3);
         transaction.originUid = 1;
         transaction.originPid = 2;
         mTracing.addQueuedTransaction(transaction);
-        std::vector<TransactionState> transactions;
+        std::vector<QueuedTransactionState> transactions;
         update.transactions.emplace_back(transaction);
         mTracing.addCommittedTransactions(vsyncId, 0, update, {}, false);
         flush();
     }
 
     void verifyEntry(const perfetto::protos::TransactionTraceEntry& actualProto,
-                     const std::vector<TransactionState>& expectedTransactions,
+                     const std::vector<QueuedTransactionState>& expectedTransactions,
                      int64_t expectedVsyncId) {
         EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId);
         ASSERT_EQ(actualProto.transactions().size(),
@@ -92,10 +92,10 @@
 };
 
 TEST_F(TransactionTracingTest, addTransactions) {
-    std::vector<TransactionState> transactions;
+    std::vector<QueuedTransactionState> transactions;
     transactions.reserve(100);
     for (uint64_t i = 0; i < 100; i++) {
-        TransactionState transaction;
+        QueuedTransactionState transaction;
         transaction.id = i;
         transaction.originPid = static_cast<int32_t>(i);
         transaction.mergedTransactionIds = std::vector<uint64_t>{i + 100, i + 102};
@@ -108,13 +108,13 @@
     int64_t firstTransactionSetVsyncId = 42;
     frontend::Update firstUpdate;
     firstUpdate.transactions =
-            std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
+            std::vector<QueuedTransactionState>(transactions.begin() + 50, transactions.end());
     mTracing.addCommittedTransactions(firstTransactionSetVsyncId, 0, firstUpdate, {}, false);
 
     int64_t secondTransactionSetVsyncId = 43;
     frontend::Update secondUpdate;
     secondUpdate.transactions =
-            std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
+            std::vector<QueuedTransactionState>(transactions.begin(), transactions.begin() + 50);
     mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false);
     flush();
 
@@ -140,7 +140,7 @@
                     getLayerCreationArgs(mChildLayerId, mParentLayerId,
                                          /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/456,
                                          /*addToRoot=*/true));
-            TransactionState transaction;
+            QueuedTransactionState transaction;
             transaction.id = 50;
             ResolvedComposerState layerState;
             layerState.layerId = mParentLayerId;
@@ -164,7 +164,7 @@
         // add transactions that modify the layer state further so we can test that layer state
         // gets merged
         {
-            TransactionState transaction;
+            QueuedTransactionState transaction;
             transaction.id = 51;
             ResolvedComposerState layerState;
             layerState.layerId = mParentLayerId;
@@ -278,7 +278,7 @@
                                          /*layerIdToMirror=*/mLayerId, /*flags=*/0,
                                          /*addToRoot=*/false));
 
-            TransactionState transaction;
+            QueuedTransactionState transaction;
             transaction.id = 50;
             ResolvedComposerState layerState;
             layerState.layerId = mLayerId;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 2bf66ac..7319f1e 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -193,6 +193,8 @@
     MOCK_METHOD(Error, getLuts,
                 (Display, const std::vector<sp<GraphicBuffer>>&,
                  std::vector<aidl::android::hardware::graphics::composer3::Luts>*));
+    MOCK_METHOD4(getLayerPresentFences,
+                 Error(Display, std::vector<Layer>*, std::vector<int>*, std::vector<int64_t>*));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h
index fd55597..5abee16 100644
--- a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h
@@ -65,6 +65,10 @@
     MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(std::shared_ptr<SessionManager>, getSessionManager, (), (override));
     MOCK_METHOD(sp<IBinder>, getOrCreateSessionManagerForBinder, (uid_t uid), (override));
+    MOCK_METHOD(void, setQueuedWorkload, (ftl::Flags<Workload> workload), (override));
+    MOCK_METHOD(void, setScreenshotWorkload, (), (override));
+    MOCK_METHOD(void, setCommittedWorkload, (ftl::Flags<Workload> workload), (override));
+    MOCK_METHOD(void, setCompositedWorkload, (ftl::Flags<Workload> workload), (override));
 };
 
 } // namespace android::adpf::mock
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index 3cb9405..8c0cce2 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -38,6 +38,12 @@
 
 namespace {
 
+/*
+ * Annotation to tell clang that we intend to fall through from one case to
+ * another in a switch. Sourced from android-base/macros.h.
+ */
+#define FALLTHROUGH_INTENDED [[clang::fallthrough]]
+
 inline bool IsIntegral(double value) {
 #if defined(ANDROID)
   // Android NDK doesn't provide std::trunc yet
diff --git a/vulkan/vkjson/vkjson.h b/vulkan/vkjson/vkjson.h
index 28de680..5818c73 100644
--- a/vulkan/vkjson/vkjson.h
+++ b/vulkan/vkjson/vkjson.h
@@ -33,12 +33,6 @@
 #undef max
 #endif
 
-/*
- * Annotation to tell clang that we intend to fall through from one case to
- * another in a switch. Sourced from android-base/macros.h.
- */
-#define FALLTHROUGH_INTENDED [[clang::fallthrough]]
-
 struct VkJsonLayer {
   VkLayerProperties properties;
   std::vector<VkExtensionProperties> extensions;