[automerger skipped] Merge "[Cherry-pick] Revert^3 "Revert "Load native GLES driver when specified.""" into main am: 287b271dd0 am: 275417eac4 -s ours am: 8b8ddebfda -s ours

am skip reason: Merged-In Ibcf9d765ce1bd8931859d1c11e849a311adda172 with SHA-1 2df5a00804 is already in history

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

Change-Id: I3563f802214a6fac7bc80e2451ed54d0ec50966a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index a7bc018..c787113 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -141,6 +141,7 @@
         bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_WEAR &&
         bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_TELEPHONY &&
         bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_WIFI &&
+        bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_ONBOARDING &&
         bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_DEFAULT) {
         MYLOGE("Invalid input: bad bugreport mode: %d", bugreport_mode);
         signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 0dc8f5a..fa9bcf3 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -49,6 +49,9 @@
     // Default mode.
     const int BUGREPORT_MODE_DEFAULT = 6;
 
+    // Bugreport taken for onboarding related flows.
+    const int BUGREPORT_MODE_ONBOARDING = 7;
+
     // Use pre-dumped data.
     const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 0x1;
 
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 6fdf1c5..33f6d38 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -2171,6 +2171,11 @@
     printf("========================================================\n");
 }
 
+// Collects a lightweight dumpstate to be used for debugging onboarding related flows.
+static void DumpstateOnboardingOnly() {
+    ds.AddDir(LOGPERSIST_DATA_DIR, false);
+}
+
 Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
     const std::string temp_file_pattern = ds.bugreport_internal_dir_ + "/dumptrace_XXXXXX";
     const size_t buf_size = temp_file_pattern.length() + 1;
@@ -2303,6 +2308,7 @@
             return dumpstate_hal_hidl::DumpstateMode::CONNECTIVITY;
         case Dumpstate::BugreportMode::BUGREPORT_WIFI:
             return dumpstate_hal_hidl::DumpstateMode::WIFI;
+        case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
         case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
             return dumpstate_hal_hidl::DumpstateMode::DEFAULT;
     }
@@ -2324,6 +2330,7 @@
             return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::CONNECTIVITY;
         case Dumpstate::BugreportMode::BUGREPORT_WIFI:
             return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::WIFI;
+        case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
         case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
             return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::DEFAULT;
     }
@@ -2807,6 +2814,8 @@
             return "BUGREPORT_TELEPHONY";
         case Dumpstate::BugreportMode::BUGREPORT_WIFI:
             return "BUGREPORT_WIFI";
+        case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
+            return "BUGREPORT_ONBOARDING";
         case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
             return "BUGREPORT_DEFAULT";
     }
@@ -2852,6 +2861,10 @@
             options->wifi_only = true;
             options->do_screenshot = false;
             break;
+        case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
+            options->onboarding_only = true;
+            options->do_screenshot = false;
+            break;
         case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
             break;
     }
@@ -3271,6 +3284,8 @@
         DumpstateWifiOnly();
     } else if (options_->limited_only) {
         DumpstateLimitedOnly();
+    } else if (options_->onboarding_only) {
+        DumpstateOnboardingOnly();
     } else {
         // Dump state for the default case. This also drops root.
         RunStatus s = DumpstateDefaultAfterCritical();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 8a31c31..0a032ec 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -201,6 +201,7 @@
         BUGREPORT_WEAR = android::os::IDumpstate::BUGREPORT_MODE_WEAR,
         BUGREPORT_TELEPHONY = android::os::IDumpstate::BUGREPORT_MODE_TELEPHONY,
         BUGREPORT_WIFI = android::os::IDumpstate::BUGREPORT_MODE_WIFI,
+        BUGREPORT_ONBOARDING = android::os::IDumpstate::BUGREPORT_MODE_ONBOARDING,
         BUGREPORT_DEFAULT = android::os::IDumpstate::BUGREPORT_MODE_DEFAULT
     };
 
@@ -412,6 +413,7 @@
         bool show_header_only = false;
         bool telephony_only = false;
         bool wifi_only = false;
+        bool onboarding_only = false;
         // Trimmed-down version of dumpstate to only include whitelisted logs.
         bool limited_only = false;
         // Whether progress updates should be published.
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 77989d1..a401838 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -308,6 +308,8 @@
         service = &(it->second);
 
         if (!service->allowIsolated && is_multiuser_uid_isolated(ctx.uid)) {
+            LOG(WARNING) << "Isolated app with UID " << ctx.uid << " requested '" << name
+                         << "', but the service is not allowed for isolated apps.";
             return nullptr;
         }
         out = service->binder;
diff --git a/cmds/sfdo/Android.bp b/cmds/sfdo/Android.bp
new file mode 100644
index 0000000..c19c9da
--- /dev/null
+++ b/cmds/sfdo/Android.bp
@@ -0,0 +1,17 @@
+cc_binary {
+    name: "sfdo",
+
+    srcs: ["sfdo.cpp"],
+
+    shared_libs: [
+        "libutils",
+        "libgui",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
new file mode 100644
index 0000000..55326ea
--- /dev/null
+++ b/cmds/sfdo/sfdo.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <inttypes.h>
+#include <stdint.h>
+#include <any>
+#include <unordered_map>
+
+#include <cutils/properties.h>
+#include <sys/resource.h>
+#include <utils/Log.h>
+
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
+#include <private/gui/ComposerServiceAIDL.h>
+
+using namespace android;
+
+std::unordered_map<std::string, std::any> g_functions;
+
+const std::unordered_map<std::string, std::string> g_function_details = {
+    {"DebugFlash", "[optional(delay)] Perform a debug flash."},
+    {"FrameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
+    {"scheduleComposite", "Force composite ahead of next VSYNC."},
+    {"scheduleCommit", "Force commit ahead of next VSYNC."},
+    {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
+};
+
+static void ShowUsage() {
+    std::cout << "usage: sfdo [help, FrameRateIndicator show, DebugFlash enabled, ...]\n\n";
+    for (const auto& sf : g_functions) {
+        const std::string fn = sf.first;
+        std::string fdetails = "TODO";
+        if (g_function_details.find(fn) != g_function_details.end())
+            fdetails = g_function_details.find(fn)->second;
+        std::cout << "    " << fn << ": " << fdetails << "\n";
+    }
+}
+
+int FrameRateIndicator([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+    bool hide = false, show = false;
+    if (argc == 3) {
+        show = strcmp(argv[2], "show") == 0;
+        hide = strcmp(argv[2], "hide") == 0;
+    }
+
+    if (show || hide) {
+        ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
+    } else {
+        std::cerr << "Incorrect usage of FrameRateIndicator. Missing [hide | show].\n";
+        return -1;
+    }
+    return 0;
+}
+
+int DebugFlash([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+    int delay = 0;
+    if (argc == 3) {
+        delay = atoi(argv[2]) == 0;
+    }
+
+    ComposerServiceAIDL::getComposerService()->setDebugFlash(delay);
+    return 0;
+}
+
+int scheduleComposite([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+    ComposerServiceAIDL::getComposerService()->scheduleComposite();
+    return 0;
+}
+
+int scheduleCommit([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+    ComposerServiceAIDL::getComposerService()->scheduleCommit();
+    return 0;
+}
+
+int main(int argc, char** argv) {
+    std::cout << "Execute SurfaceFlinger internal commands.\n";
+    std::cout << "sfdo requires to be run with root permissions..\n";
+
+    g_functions["FrameRateIndicator"] = FrameRateIndicator;
+    g_functions["DebugFlash"] = DebugFlash;
+    g_functions["scheduleComposite"] = scheduleComposite;
+    g_functions["scheduleCommit"] = scheduleCommit;
+
+    if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
+        std::cout << "Running: " << argv[1] << "\n";
+        const std::string key(argv[1]);
+        const auto fn = g_functions[key];
+        int result = std::any_cast<int (*)(int, char**)>(fn)(argc, argv);
+        if (result == 0) {
+            std::cout << "Success.\n";
+        }
+        return result;
+    } else {
+        ShowUsage();
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 1418e1f..e286a84 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -275,12 +275,6 @@
 }
 
 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.thread_network.prebuilt.xml",
     src: "android.hardware.thread_network.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
deleted file mode 100644
index d36c958..0000000
--- a/data/etc/android.hardware.telephony.satellite.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- Feature for devices that support satellite communication via satellite vendor service APIs. -->
-<permissions>
-    <feature name="android.hardware.telephony.satellite" />
-</permissions>
diff --git a/data/etc/android.hardware.type.automotive.xml b/data/etc/android.hardware.type.automotive.xml
index a9b4b05..8605d18 100644
--- a/data/etc/android.hardware.type.automotive.xml
+++ b/data/etc/android.hardware.type.automotive.xml
@@ -17,4 +17,6 @@
 <!-- These features determine that the device running android is a car. -->
 <permissions>
     <feature name="android.hardware.type.automotive" />
+
+    <unavailable-feature name="android.software.picture_in_picture"/>
 </permissions>
diff --git a/include/android/input.h b/include/android/input.h
index 9a0eb4d..16d86af 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -54,16 +54,12 @@
 #include <stdint.h>
 #include <sys/types.h>
 #include <android/keycodes.h>
-
-// This file is included by modules that have host support but android/looper.h is not supported
-// on host. __REMOVED_IN needs to be defined in order for android/looper.h to be compiled.
-#ifndef __BIONIC__
-#define __REMOVED_IN(x) __attribute__((deprecated))
-#endif
 #include <android/looper.h>
 
 #include <jni.h>
 
+// This file may also be built on glibc or on Windows/MacOS libc's, so no-op
+// definitions are provided.
 #if !defined(__INTRODUCED_IN)
 #define __INTRODUCED_IN(__api_level) /* nothing */
 #endif
diff --git a/include/android/looper.h b/include/android/looper.h
index 4fe142a..e50730d 100644
--- a/include/android/looper.h
+++ b/include/android/looper.h
@@ -26,10 +26,18 @@
 #ifndef ANDROID_LOOPER_H
 #define ANDROID_LOOPER_H
 
+#include <sys/cdefs.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+// This file may also be built on glibc or on Windows/MacOS libc's, so
+// deprecated definitions are provided.
+#if !defined(__REMOVED_IN)
+#define __REMOVED_IN(__api_level) __attribute__((__deprecated__))
+#endif
+
 struct ALooper;
 /**
  * ALooper
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index b494f89..ba8b02d 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -14,6 +14,23 @@
  * limitations under the License.
  */
 
+ /**
+ * @defgroup APerformanceHint Performance Hint Manager
+ *
+ * APerformanceHint allows apps to create performance hint sessions for groups
+ * of threads, and provide hints to the system about the workload of those threads,
+ * to help the system more accurately allocate power for them. It is the NDK
+ * counterpart to the Java PerformanceHintManager SDK API.
+ *
+ * @{
+ */
+
+/**
+ * @file performance_hint.h
+ * @brief API for creating and managing a hint session.
+ */
+
+
 #ifndef ANDROID_NATIVE_PERFORMANCE_HINT_H
 #define ANDROID_NATIVE_PERFORMANCE_HINT_H
 
@@ -48,7 +65,7 @@
  * An opaque type representing a handle to a performance hint manager.
  * It must be released after use.
  *
- * <p>To use:<ul>
+ * To use:<ul>
  *    <li>Obtain the performance hint manager instance by calling
  *        {@link APerformanceHint_getManager} function.</li>
  *    <li>Create an {@link APerformanceHintSession} with
@@ -61,50 +78,43 @@
 /**
  * An opaque type representing a handle to a performance hint session.
  * A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
+ * with {@link APerformanceHint_createSession}. It must be
  * freed with {@link APerformanceHint_closeSession} after use.
  *
  * A Session represents a group of threads with an inter-related workload such that hints for
  * their performance should be considered as a unit. The threads in a given session should be
- * long-life and not created or destroyed dynamically.
+ * long-lived and not created or destroyed dynamically.
  *
- * <p>Each session is expected to have a periodic workload with a target duration for each
- * cycle. The cycle duration is likely greater than the target work duration to allow other
- * parts of the pipeline to run within the available budget. For example, a renderer thread may
- * work at 60hz in order to produce frames at the display's frame but have a target work
- * duration of only 6ms.</p>
+ * The work duration API can be used with periodic workloads to dynamically adjust thread
+ * performance and keep the work on schedule while optimizing the available power budget.
+ * When using the work duration API, the starting target duration should be specified
+ * while creating the session, and can later be adjusted with
+ * {@link APerformanceHint_updateTargetWorkDuration}. While using the work duration
+ * API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
+ * cycle to report the actual time taken to complete to the system.
  *
- * <p>After each cycle of work, the client is expected to use
- * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
- * complete.</p>
- *
- * <p>To use:<ul>
- *    <li>Update a sessions target duration for each cycle of work
- *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
- *    <li>Report the actual duration for the last cycle of work with
- *        {@link APerformanceHint_reportActualWorkDuration}.</li>
- *    <li>Release the session instance with
- *        {@link APerformanceHint_closeSession}.</li></ul></p>
+ * All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
 
 /**
   * Acquire an instance of the performance hint manager.
   *
-  * @return manager instance on success, nullptr on failure.
+  * @return APerformanceHintManager instance on success, nullptr on failure.
   */
 APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Creates a session for the given set of threads and sets their initial target work
  * duration.
+ *
  * @param manager The performance hint manager instance.
  * @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 threadIds.
- * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
- *     This must be positive.
- * @return manager instance on success, nullptr on failure.
+ *     this process' thread group.
+ * @param size The size of the list of threadIds.
+ * @param initialTargetWorkDurationNanos The target duration in nanoseconds for the new session.
+ *     This must be positive if using the work duration API, or 0 otherwise.
+ * @return APerformanceHintManager instance on success, nullptr on failure.
  */
 APerformanceHintSession* APerformanceHint_createSession(
         APerformanceHintManager* manager,
@@ -124,8 +134,8 @@
  * 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.
- * @return 0 on success
+ * @param targetDurationNanos The new desired duration in nanoseconds. This must be positive.
+ * @return 0 on success.
  *         EINVAL if targetDurationNanos is not positive.
  *         EPIPE if communication with the system service has failed.
  */
@@ -136,14 +146,13 @@
 /**
  * Reports the actual duration for the last cycle of work.
  *
- * <p>The system will attempt to adjust the core placement of the threads within the thread
- * group and/or the frequency of the core on which they are run to bring the actual duration
- * close to the target duration.</p>
+ * The system will attempt to adjust the scheduling and performance of the
+ * threads within the thread group to bring the actual duration close to the target duration.
  *
  * @param session The performance hint session instance to update.
- * @param actualDurationNanos how long the thread group took to complete its last task in
- *     nanoseconds. This must be positive.
- * @return 0 on success
+ * @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.
  *         EPIPE if communication with the system service has failed.
  */
@@ -164,19 +173,36 @@
  * 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.
  *
- * @param session The performance hint session instance for the threads.
+ * @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.
+ * @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 is not part of the thread group.
+ *         EINVAL if the list of thread ids is empty or if any of the thread ids are not part of
+               the thread group.
  *         EPIPE if communication with the system service has failed.
+ *         EPERM if any thread id doesn't belong to the application.
  */
 int APerformanceHint_setThreads(
         APerformanceHintSession* session,
         const pid_t* threadIds,
         size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
 
+/**
+ * This tells the session that these threads can be
+ * safely scheduled to prefer power efficiency over performance.
+ *
+ * @param session The performance hint session instance to update.
+ * @param enabled The flag which sets whether this session will use power-efficient scheduling.
+ * @return 0 on success.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_setPreferPowerEfficiency(
+        APerformanceHintSession* session,
+        bool enabled) __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
+
+/** @} */
\ No newline at end of file
diff --git a/include/android/sensor.h b/include/android/sensor.h
index 16c5dde..a618393 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -29,6 +29,8 @@
 #ifndef ANDROID_SENSOR_H
 #define ANDROID_SENSOR_H
 
+#include <sys/cdefs.h>
+
 /******************************************************************
  *
  * IMPORTANT NOTICE:
@@ -45,11 +47,6 @@
  *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
  */
 
-// This file is included by modules that have host support but android/looper.h is not supported
-// on host. __REMOVED_IN needs to be defined in order for android/looper.h to be compiled.
-#ifndef __BIONIC__
-#define __REMOVED_IN(x) __attribute__((deprecated))
-#endif
 #include <android/looper.h>
 
 #include <stdbool.h>
@@ -57,6 +54,8 @@
 #include <math.h>
 #include <stdint.h>
 
+// This file may also be built on glibc or on Windows/MacOS libc's, so no-op
+// and deprecated definitions are provided.
 #if !defined(__INTRODUCED_IN)
 #define __INTRODUCED_IN(__api_level) /* nothing */
 #endif
@@ -658,7 +657,7 @@
     uint32_t flags;
     int32_t reserved1[3];
 } ASensorEvent;
-// LINT.ThenChange (hardware/libhardware/include/hardware/sensors.h)
+// LINT.ThenChange(hardware/libhardware/include/hardware/sensors.h)
 
 struct ASensorManager;
 /**
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 075d12b..2c86e2e 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -25,12 +25,12 @@
 
 #include <ftl/string.h>
 
-// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the
-// compiler-generated string literal for the signature of this function. The function is defined in
-// the global namespace with a short name and inferred return type to reduce bloat in the read-only
-// data segment.
-template <typename E, E V>
-constexpr auto ftl_enum() {
+// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as
+// std::optional<std::string_view> by parsing the compiler-generated string literal for the
+// signature of this function. The function is defined in the global namespace with a short name
+// and inferred return type to reduce bloat in the read-only data segment.
+template <bool S, typename E, E V>
+constexpr auto ftl_enum_builder() {
   static_assert(std::is_enum_v<E>);
 
   using R = std::optional<std::string_view>;
@@ -58,7 +58,9 @@
   //   V = android::test::Enum::kValue
   //
   view = view.substr(value_begin);
-  const auto name_begin = view.rfind("::"sv);
+  const auto pos = S ? view.rfind("::"sv) - 2 : view.npos;
+
+  const auto name_begin = view.rfind("::"sv, pos);
   if (name_begin == view.npos) return R{};
 
   // Chop off the leading "::".
@@ -68,6 +70,18 @@
   return name.find(')') == view.npos ? R{name} : R{};
 }
 
+// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum() {
+  return ftl_enum_builder<false, E, V>();
+}
+
+// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum_full() {
+  return ftl_enum_builder<true, E, V>();
+}
+
 namespace android::ftl {
 
 // Trait for determining whether a type is specifically a scoped enum or not. By definition, a
@@ -191,6 +205,11 @@
   static constexpr auto value = ftl_enum<decltype(V), V>();
 };
 
+template <auto V>
+struct EnumNameFull {
+  static constexpr auto value = ftl_enum_full<decltype(V), V>();
+};
+
 template <auto I>
 struct FlagName {
   using E = decltype(I);
@@ -230,6 +249,18 @@
   return *kName;
 }
 
+// Returns a stringified enumerator with class at compile time.
+//
+//   enum class E { A, B, C };
+//   static_assert(ftl::enum_name<E::B>() == "E::B");
+//
+template <auto V>
+constexpr std::string_view enum_name_full() {
+  constexpr auto kName = ftl_enum_full<decltype(V), V>();
+  static_assert(kName, "Unknown enumerator");
+  return *kName;
+}
+
 // Returns a stringified enumerator, possibly at compile time.
 //
 //   enum class E { A, B, C, F = 5, ftl_last = F };
@@ -249,6 +280,25 @@
   return kRange.values[value - kBegin];
 }
 
+// Returns a stringified enumerator with class, possibly at compile time.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   static_assert(ftl::enum_name(E::C).value_or("?") == "E::C");
+//   static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+//
+template <typename E>
+constexpr std::optional<std::string_view> enum_name_full(E v) {
+  const auto value = to_underlying(v);
+
+  constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  constexpr auto kLast = to_underlying(enum_last_v<E>);
+  if (value < kBegin || value > kLast) return {};
+
+  constexpr auto kRange = details::EnumRange<E, details::EnumNameFull>{};
+  return kRange.values[value - kBegin];
+}
+
 // Returns a stringified flag enumerator, possibly at compile time.
 //
 //   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
@@ -282,6 +332,21 @@
   return to_string(to_underlying(v));
 }
 
+// Returns a stringified enumerator with class, or its integral value if not named.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   assert(ftl::enum_string(E::C) == "E::C");
+//   assert(ftl::enum_string(E{3}) == "3");
+//
+template <typename E>
+inline std::string enum_string_full(E v) {
+  if (const auto name = enum_name_full(v)) {
+      return std::string(*name);
+  }
+  return to_string(to_underlying(v));
+}
+
 // Returns a stringified flag enumerator, or its integral value if not named.
 //
 //   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
diff --git a/include/input/Input.h b/include/input/Input.h
index 527a477..bd544b5 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -257,6 +257,10 @@
 
 bool isStylusToolType(ToolType toolType);
 
+struct PointerProperties;
+
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties);
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
@@ -282,8 +286,19 @@
 
     // Indicates that the key represents a special gesture that has been detected by
     // the touch firmware or driver.  Causes touch events from the same device to be canceled.
+    // This policy flag prevents key events from changing touch mode state.
     POLICY_FLAG_GESTURE = 0x00000008,
 
+    // Indicates that key usage mapping represents a fallback mapping.
+    // Fallback mappings cannot be used to definitively determine whether a device
+    // supports a key code. For example, a HID device can report a key press
+    // as a HID usage code if it is not mapped to any linux key code in the kernel.
+    // However, we cannot know which HID usage codes that device supports from
+    // userspace through the evdev. We can use fallback mappings to convert HID
+    // usage codes to Android key codes without needing to know if a device can
+    // actually report the usage code.
+    POLICY_FLAG_FALLBACK_USAGE_MAPPING = 0x00000010,
+
     POLICY_FLAG_RAW_MASK = 0x0000ffff,
 
 #ifdef __linux__
@@ -492,14 +507,17 @@
         toolType = ToolType::UNKNOWN;
     }
 
-    bool operator==(const PointerProperties& other) const;
+    bool operator==(const PointerProperties& other) const = default;
     inline bool operator!=(const PointerProperties& other) const {
         return !(*this == other);
     }
 
-    void copyFrom(const PointerProperties& other);
+    PointerProperties& operator=(const PointerProperties&) = default;
 };
 
+// TODO(b/211379801) : Use a strong type from ftl/mixins.h instead
+using DeviceId = int32_t;
+
 /*
  * Input events.
  */
@@ -511,7 +529,7 @@
 
     inline int32_t getId() const { return mId; }
 
-    inline int32_t getDeviceId() const { return mDeviceId; }
+    inline DeviceId getDeviceId() const { return mDeviceId; }
 
     inline uint32_t getSource() const { return mSource; }
 
@@ -526,13 +544,13 @@
     static int32_t nextId();
 
 protected:
-    void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
                     std::array<uint8_t, 32> hmac);
 
     void initialize(const InputEvent& from);
 
     int32_t mId;
-    int32_t mDeviceId;
+    DeviceId mDeviceId;
     uint32_t mSource;
     int32_t mDisplayId;
     std::array<uint8_t, 32> mHmac;
@@ -547,7 +565,7 @@
 public:
     virtual ~KeyEvent() { }
 
-    virtual InputEventType getType() const { return InputEventType::KEY; }
+    InputEventType getType() const override { return InputEventType::KEY; }
 
     inline int32_t getAction() const { return mAction; }
 
@@ -570,7 +588,7 @@
     static const char* getLabel(int32_t keyCode);
     static std::optional<int> getKeyCodeFromLabel(const char* label);
 
-    void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
                     std::array<uint8_t, 32> hmac, int32_t action, int32_t flags, int32_t keyCode,
                     int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                     nsecs_t eventTime);
@@ -598,7 +616,7 @@
 public:
     virtual ~MotionEvent() { }
 
-    virtual InputEventType getType() const { return InputEventType::MOTION; }
+    InputEventType getType() const override { return InputEventType::MOTION; }
 
     inline int32_t getAction() const { return mAction; }
 
@@ -834,7 +852,7 @@
 
     ssize_t findPointerIndex(int32_t pointerId) const;
 
-    void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
                     std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton,
                     int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                     MotionClassification classification, const ui::Transform& transform,
@@ -926,7 +944,7 @@
 public:
     virtual ~FocusEvent() {}
 
-    virtual InputEventType getType() const override { return InputEventType::FOCUS; }
+    InputEventType getType() const override { return InputEventType::FOCUS; }
 
     inline bool getHasFocus() const { return mHasFocus; }
 
@@ -945,7 +963,7 @@
 public:
     virtual ~CaptureEvent() {}
 
-    virtual InputEventType getType() const override { return InputEventType::CAPTURE; }
+    InputEventType getType() const override { return InputEventType::CAPTURE; }
 
     inline bool getPointerCaptureEnabled() const { return mPointerCaptureEnabled; }
 
@@ -964,7 +982,7 @@
 public:
     virtual ~DragEvent() {}
 
-    virtual InputEventType getType() const override { return InputEventType::DRAG; }
+    InputEventType getType() const override { return InputEventType::DRAG; }
 
     inline bool isExiting() const { return mIsExiting; }
 
@@ -988,7 +1006,7 @@
 public:
     virtual ~TouchModeEvent() {}
 
-    virtual InputEventType getType() const override { return InputEventType::TOUCH_MODE; }
+    InputEventType getType() const override { return InputEventType::TOUCH_MODE; }
 
     inline bool isInTouchMode() const { return mIsInTouchMode; }
 
@@ -1012,7 +1030,7 @@
     };
 
     Type type;
-    int32_t deviceId;
+    DeviceId deviceId;
     nsecs_t eventTimeNanos;
     uint32_t source;
     int32_t displayId;
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index 909bf08..44247c1 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -69,6 +69,12 @@
 
     static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value);
 
+    static std::optional<int> getLinuxEvdevEventTypeByLabel(const char* label);
+
+    static std::optional<int> getLinuxEvdevEventCodeByLabel(int32_t type, const char* label);
+
+    static std::optional<int> getLinuxEvdevInputPropByLabel(const char* label);
+
 private:
     InputEventLookup();
 
diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h
index 3715408..14dd463 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -46,11 +46,13 @@
 public:
     InputVerifier(const std::string& name);
 
-    android::base::Result<void> processMovement(int32_t deviceId, int32_t action,
+    android::base::Result<void> processMovement(int32_t deviceId, int32_t source, int32_t action,
                                                 uint32_t pointerCount,
                                                 const PointerProperties* pointerProperties,
                                                 const PointerCoords* pointerCoords, int32_t flags);
 
+    void resetDevice(int32_t deviceId);
+
 private:
     rust::Box<android::input::verifier::InputVerifier> mVerifier;
 };
diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h
index 8c3c74a..b126abe 100644
--- a/include/input/KeyLayoutMap.h
+++ b/include/input/KeyLayoutMap.h
@@ -17,13 +17,13 @@
 #pragma once
 
 #include <android-base/result.h>
+#include <input/InputDevice.h>
+
 #include <stdint.h>
 #include <utils/Errors.h>
 #include <utils/Tokenizer.h>
 #include <set>
 
-#include <input/InputDevice.h>
-
 namespace android {
 
 struct AxisInfo {
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 0e3fbb1..63c0e40 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -75,11 +75,12 @@
 }
 
 /**
- * Convert a map to string. Both keys and values of the map should be integral type.
+ * Convert a map or multimap to string. Both keys and values of the map should be integral type.
  */
-template <typename K, typename V>
-std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString,
-                    std::string (*valueToString)(const V&) = constToString) {
+template <typename T>
+std::string dumpMap(const T& map,
+                    std::string (*keyToString)(const typename T::key_type&) = constToString,
+                    std::string (*valueToString)(const typename T::mapped_type&) = constToString) {
     std::string out;
     for (const auto& [k, v] : map) {
         if (!out.empty()) {
@@ -104,15 +105,13 @@
     return out.empty() ? "{}" : (out + "}");
 }
 
-/**
- * Convert a vector to a string. The values of the vector should be of a type supported by
- * constToString.
- */
+/** Convert a vector to a string. */
 template <typename T>
-std::string dumpVector(std::vector<T> values) {
-    std::string dump = constToString(values[0]);
+std::string dumpVector(const std::vector<T>& values,
+                       std::string (*valueToString)(const T&) = constToString) {
+    std::string dump = valueToString(values[0]);
     for (size_t i = 1; i < values.size(); i++) {
-        dump += ", " + constToString(values[i]);
+        dump += ", " + valueToString(values[i]);
     }
     return dump;
 }
diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h
index 37fe5af..d2747d6 100644
--- a/include/input/RingBuffer.h
+++ b/include/input/RingBuffer.h
@@ -24,7 +24,6 @@
 #include <type_traits>
 #include <utility>
 
-#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
 namespace android {
@@ -277,15 +276,16 @@
 
     // Converts the index of an element in [0, size()] to its corresponding index in mBuffer.
     size_type bufferIndex(size_type elementIndex) const {
-        CHECK_LE(elementIndex, size());
+        if (elementIndex > size()) {
+            abort();
+        }
         size_type index = mBegin + elementIndex;
         if (index >= capacity()) {
             index -= capacity();
         }
-        CHECK_LT(index, capacity())
-                << android::base::StringPrintf("Invalid index calculated for element (%zu) "
-                                               "in buffer of size %zu",
-                                               elementIndex, size());
+        if (index >= capacity()) {
+            abort();
+        }
         return index;
     }
 
diff --git a/include/input/TraceTools.h b/include/input/TraceTools.h
new file mode 100644
index 0000000..81fc7af
--- /dev/null
+++ b/include/input/TraceTools.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/Trace.h>
+#include <optional>
+
+// A macro for tracing when the given condition is true.
+// This macro relies on the fact that only one branch of the ternary operator is evaluated. That
+// means if `message` is an expression that evaluates to a std::string value, the value will
+// not be computed unless the condition is true.
+#define ATRACE_NAME_IF(condition, message)                                            \
+    const auto _trace_token = condition                                               \
+            ? std::make_optional<android::ScopedTrace>(ATRACE_TAG, (message).c_str()) \
+            : std::nullopt
diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h
index f3c201e..b78f63e 100644
--- a/include/input/VelocityControl.h
+++ b/include/input/VelocityControl.h
@@ -88,7 +88,7 @@
     VelocityControl();
 
     /* Gets the various parameters. */
-    VelocityControlParameters& getParameters();
+    const VelocityControlParameters& getParameters() const;
 
     /* Sets the various parameters. */
     void setParameters(const VelocityControlParameters& parameters);
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 4257cb5..2e99495 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -16,7 +16,9 @@
 
 #pragma once
 
+#include <android/os/IInputConstants.h>
 #include <input/Input.h>
+#include <input/RingBuffer.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
 #include <map>
@@ -31,40 +33,25 @@
  */
 class VelocityTracker {
 public:
+    static const size_t MAX_DEGREE = 4;
+
     enum class Strategy : int32_t {
-        DEFAULT = -1,
-        MIN = 0,
-        IMPULSE = 0,
-        LSQ1 = 1,
-        LSQ2 = 2,
-        LSQ3 = 3,
-        WLSQ2_DELTA = 4,
-        WLSQ2_CENTRAL = 5,
-        WLSQ2_RECENT = 6,
-        INT1 = 7,
-        INT2 = 8,
-        LEGACY = 9,
+        DEFAULT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_DEFAULT,
+        IMPULSE = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_IMPULSE,
+        LSQ1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ1,
+        LSQ2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ2,
+        LSQ3 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ3,
+        WLSQ2_DELTA = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA,
+        WLSQ2_CENTRAL = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL,
+        WLSQ2_RECENT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT,
+        INT1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT1,
+        INT2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT2,
+        LEGACY = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LEGACY,
+        MIN = IMPULSE,
         MAX = LEGACY,
         ftl_last = LEGACY,
     };
 
-    struct Estimator {
-        static const size_t MAX_DEGREE = 4;
-
-        // Estimator time base.
-        nsecs_t time = 0;
-
-        // Polynomial coefficients describing motion.
-        std::array<float, MAX_DEGREE + 1> coeff{};
-
-        // Polynomial degree (number of coefficients), or zero if no information is
-        // available.
-        uint32_t degree = 0;
-
-        // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
-        float confidence = 0;
-    };
-
     /*
      * Contains all available velocity data from a VelocityTracker.
      */
@@ -123,11 +110,6 @@
     // [-maxVelocity, maxVelocity], inclusive.
     ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity);
 
-    // Gets an estimator for the recent movements of the specified pointer id for the given axis.
-    // Returns false and clears the estimator if there is no information available
-    // about the pointer.
-    std::optional<Estimator> getEstimator(int32_t axis, int32_t pointerId) const;
-
     // Gets the active pointer id, or -1 if none.
     inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); }
 
@@ -168,14 +150,48 @@
 
     virtual void clearPointer(int32_t pointerId) = 0;
     virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0;
-    virtual std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const = 0;
+    virtual std::optional<float> getVelocity(int32_t pointerId) const = 0;
 };
 
+/**
+ * A `VelocityTrackerStrategy` that accumulates added data points and processes the accumulated data
+ * points when getting velocity.
+ */
+class AccumulatingVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+    AccumulatingVelocityTrackerStrategy(nsecs_t horizonNanos, bool maintainHorizonDuringAdd);
+
+    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+    void clearPointer(int32_t pointerId) override;
+
+protected:
+    struct Movement {
+        nsecs_t eventTime;
+        float position;
+    };
+
+    // Number of samples to keep.
+    // If different strategies would like to maintain different history size, we can make this a
+    // protected const field.
+    static constexpr uint32_t HISTORY_SIZE = 20;
+
+    /**
+     * Duration, in nanoseconds, since the latest movement where a movement may be considered for
+     * velocity calculation.
+     */
+    const nsecs_t mHorizonNanos;
+    /**
+     * If true, data points outside of horizon (see `mHorizonNanos`) will be cleared after each
+     * addition of a new movement.
+     */
+    const bool mMaintainHorizonDuringAdd;
+    std::map<int32_t /*pointerId*/, RingBuffer<Movement>> mMovements;
+};
 
 /*
  * Velocity tracker algorithm based on least-squares linear regression.
  */
-class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
+class LeastSquaresVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
 public:
     enum class Weighting {
         // No weights applied.  All data points are equally reliable.
@@ -192,13 +208,11 @@
         RECENT,
     };
 
-    // Degree must be no greater than Estimator::MAX_DEGREE.
+    // Degree must be no greater than VelocityTracker::MAX_DEGREE.
     LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE);
     ~LeastSquaresVelocityTrackerStrategy() override;
 
-    void clearPointer(int32_t pointerId) override;
-    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
-    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
+    std::optional<float> getVelocity(int32_t pointerId) const override;
 
 private:
     // Sample horizon.
@@ -206,23 +220,19 @@
     // changes in direction.
     static const nsecs_t HORIZON = 100 * 1000000; // 100 ms
 
-    // Number of samples to keep.
-    static const uint32_t HISTORY_SIZE = 20;
-
-    struct Movement {
-        nsecs_t eventTime;
-        float position;
-    };
-
     float chooseWeight(int32_t pointerId, uint32_t index) const;
+    /**
+     * An optimized least-squares solver for degree 2 and no weight (i.e. `Weighting.NONE`).
+     * The provided container of movements shall NOT be empty, and shall have the movements in
+     * chronological order.
+     */
+    std::optional<float> solveUnweightedLeastSquaresDeg2(
+            const RingBuffer<Movement>& movements) const;
 
     const uint32_t mDegree;
     const Weighting mWeighting;
-    std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
-    std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
 };
 
-
 /*
  * Velocity tracker algorithm that uses an IIR filter.
  */
@@ -234,7 +244,7 @@
 
     void clearPointer(int32_t pointerId) override;
     void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override;
-    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
+    std::optional<float> getVelocity(int32_t pointerId) const override;
 
 private:
     // Current state estimate for a particular pointer.
@@ -251,49 +261,33 @@
 
     void initState(State& state, nsecs_t eventTime, float pos) const;
     void updateState(State& state, nsecs_t eventTime, float pos) const;
-    void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
 };
 
 
 /*
  * Velocity tracker strategy used prior to ICS.
  */
-class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
+class LegacyVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
 public:
     LegacyVelocityTrackerStrategy();
     ~LegacyVelocityTrackerStrategy() override;
 
-    void clearPointer(int32_t pointerId) override;
-    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
-    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
+    std::optional<float> getVelocity(int32_t pointerId) const override;
 
 private:
     // Oldest sample to consider when calculating the velocity.
     static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
 
-    // Number of samples to keep.
-    static const uint32_t HISTORY_SIZE = 20;
-
     // The minimum duration between samples when estimating velocity.
     static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
-
-    struct Movement {
-        nsecs_t eventTime;
-        float position;
-    };
-
-    std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
-    std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
 };
 
-class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy {
+class ImpulseVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
 public:
     ImpulseVelocityTrackerStrategy(bool deltaValues);
     ~ImpulseVelocityTrackerStrategy() override;
 
-    void clearPointer(int32_t pointerId) override;
-    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
-    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
+    std::optional<float> getVelocity(int32_t pointerId) const override;
 
 private:
     // Sample horizon.
@@ -301,21 +295,10 @@
     // changes in direction.
     static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms
 
-    // Number of samples to keep.
-    static constexpr size_t HISTORY_SIZE = 20;
-
-    struct Movement {
-        nsecs_t eventTime;
-        float position;
-    };
-
     // Whether or not the input movement values for the strategy come in the form of delta values.
     // If the input values are not deltas, the strategy needs to calculate deltas as part of its
     // velocity calculation.
     const bool mDeltaValues;
-
-    std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
-    std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
 };
 
 } // namespace android
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index 82e4551..9e426d3 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -17,11 +17,11 @@
 #ifndef ANDROID_POWERHALCONTROLLER_H
 #define ANDROID_POWERHALCONTROLLER_H
 
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android-base/thread_annotations.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
 #include <powermanager/PowerHalWrapper.h>
 
 namespace android {
@@ -55,11 +55,13 @@
 
     virtual void init();
 
-    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
-    virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
-            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-            int64_t durationNanos) override;
+    virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+                                     int32_t durationMs) override;
+    virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode,
+                                    bool enabled) override;
+    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                      int64_t durationNanos) override;
     virtual HalResult<int64_t> getHintSessionPreferredRate() override;
 
 private:
diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h
index e0384f3..cbbfa59 100644
--- a/include/powermanager/PowerHalLoader.h
+++ b/include/powermanager/PowerHalLoader.h
@@ -17,11 +17,11 @@
 #ifndef ANDROID_POWERHALLOADER_H
 #define ANDROID_POWERHALLOADER_H
 
+#include <aidl/android/hardware/power/IPower.h>
 #include <android-base/thread_annotations.h>
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
-#include <android/hardware/power/IPower.h>
 
 namespace android {
 
@@ -31,7 +31,7 @@
 class PowerHalLoader {
 public:
     static void unloadAll();
-    static sp<hardware::power::IPower> loadAidl();
+    static std::shared_ptr<aidl::android::hardware::power::IPower> loadAidl();
     static sp<hardware::power::V1_0::IPower> loadHidlV1_0();
     static sp<hardware::power::V1_1::IPower> loadHidlV1_1();
     static sp<hardware::power::V1_2::IPower> loadHidlV1_2();
@@ -39,7 +39,7 @@
 
 private:
     static std::mutex gHalMutex;
-    static sp<hardware::power::IPower> gHalAidl GUARDED_BY(gHalMutex);
+    static std::shared_ptr<aidl::android::hardware::power::IPower> gHalAidl GUARDED_BY(gHalMutex);
     static sp<hardware::power::V1_0::IPower> gHalHidlV1_0 GUARDED_BY(gHalMutex);
     static sp<hardware::power::V1_1::IPower> gHalHidlV1_1 GUARDED_BY(gHalMutex);
     static sp<hardware::power::V1_2::IPower> gHalHidlV1_2 GUARDED_BY(gHalMutex);
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index 8028aa8..4e4a1b0 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -17,14 +17,15 @@
 #ifndef ANDROID_POWERHALWRAPPER_H
 #define ANDROID_POWERHALWRAPPER_H
 
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android-base/thread_annotations.h>
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
+#include <binder/Status.h>
 
 namespace android {
 
@@ -47,7 +48,7 @@
     }
     static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
 
-    static HalResult<T> fromStatus(binder::Status status, T data) {
+    static HalResult<T> fromStatus(const binder::Status& status, T data) {
         if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
             return HalResult<T>::unsupported();
         }
@@ -56,14 +57,28 @@
         }
         return HalResult<T>::failed(std::string(status.toString8().c_str()));
     }
-    static HalResult<T> fromStatus(hardware::power::V1_0::Status status, T data);
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T data) {
+        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+            return HalResult<T>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<T>::ok(data);
+        }
+        return HalResult<T>::failed(std::string(status.getDescription()));
+    }
 
     template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data);
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
+        return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
+    }
 
     template <typename R>
     static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T data);
+                                   T data) {
+        return ret.isOk() ? HalResult<T>::fromStatus(status, data)
+                          : HalResult<T>::failed(ret.description());
+    }
 
     // This will throw std::bad_optional_access if this result is not ok.
     const T& value() const { return mValue.value(); }
@@ -91,12 +106,30 @@
     static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
     static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
 
-    static HalResult<void> fromStatus(status_t status);
-    static HalResult<void> fromStatus(binder::Status status);
-    static HalResult<void> fromStatus(hardware::power::V1_0::Status status);
+    static HalResult<void> fromStatus(const binder::Status& status) {
+        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+            return HalResult<void>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<void>::ok();
+        }
+        return HalResult<void>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<void> fromStatus(const ndk::ScopedAStatus& status) {
+        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+            return HalResult<void>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<void>::ok();
+        }
+        return HalResult<void>::failed(std::string(status.getDescription()));
+    }
 
     template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret);
+    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
+    }
 
     bool isOk() const { return !mUnsupported && !mFailed; }
     bool isFailed() const { return !mUnsupported && mFailed; }
@@ -119,11 +152,12 @@
 public:
     virtual ~HalWrapper() = default;
 
-    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) = 0;
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) = 0;
-    virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
-            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-            int64_t durationNanos) = 0;
+    virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+                                     int32_t durationMs) = 0;
+    virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) = 0;
+    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                      int64_t durationNanos) = 0;
     virtual HalResult<int64_t> getHintSessionPreferredRate() = 0;
 };
 
@@ -131,14 +165,15 @@
 class EmptyHalWrapper : public HalWrapper {
 public:
     EmptyHalWrapper() = default;
-    ~EmptyHalWrapper() = default;
+    ~EmptyHalWrapper() override = default;
 
-    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
-    virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+                             int32_t durationMs) override;
+    HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<int64_t> getHintSessionPreferredRate() override;
 };
 
 // Wrapper for the HIDL Power HAL v1.0.
@@ -146,14 +181,15 @@
 public:
     explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> handleV1_0)
           : mHandleV1_0(std::move(handleV1_0)) {}
-    virtual ~HidlHalWrapperV1_0() = default;
+    ~HidlHalWrapperV1_0() override = default;
 
-    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
-    virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+                             int32_t durationMs) override;
+    HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<int64_t> getHintSessionPreferredRate() override;
 
 protected:
     const sp<hardware::power::V1_0::IPower> mHandleV1_0;
@@ -167,67 +203,71 @@
 // Wrapper for the HIDL Power HAL v1.1.
 class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
 public:
-    HidlHalWrapperV1_1(sp<hardware::power::V1_1::IPower> handleV1_1)
+    explicit HidlHalWrapperV1_1(sp<hardware::power::V1_1::IPower> handleV1_1)
           : HidlHalWrapperV1_0(std::move(handleV1_1)) {}
-    virtual ~HidlHalWrapperV1_1() = default;
+    ~HidlHalWrapperV1_1() override = default;
 
 protected:
-    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId,
-                                          uint32_t data) override;
+    HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override;
 };
 
 // Wrapper for the HIDL Power HAL v1.2.
 class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
 public:
-    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
-    HidlHalWrapperV1_2(sp<hardware::power::V1_2::IPower> handleV1_2)
+    HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+                             int32_t durationMs) override;
+    HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+    explicit HidlHalWrapperV1_2(sp<hardware::power::V1_2::IPower> handleV1_2)
           : HidlHalWrapperV1_1(std::move(handleV1_2)) {}
-    virtual ~HidlHalWrapperV1_2() = default;
+    ~HidlHalWrapperV1_2() override = default;
 
 protected:
-    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId,
-                                          uint32_t data) override;
+    HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override;
 };
 
 // Wrapper for the HIDL Power HAL v1.3.
 class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 {
 public:
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
-    HidlHalWrapperV1_3(sp<hardware::power::V1_3::IPower> handleV1_3)
+    HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+    explicit HidlHalWrapperV1_3(sp<hardware::power::V1_3::IPower> handleV1_3)
           : HidlHalWrapperV1_2(std::move(handleV1_3)) {}
-    virtual ~HidlHalWrapperV1_3() = default;
+    ~HidlHalWrapperV1_3() override = default;
 
 protected:
-    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId,
-                                          uint32_t data) override;
+    HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override;
 };
 
 // Wrapper for the AIDL Power HAL.
 class AidlHalWrapper : public HalWrapper {
 public:
-    explicit AidlHalWrapper(sp<hardware::power::IPower> handle) : mHandle(std::move(handle)) {}
-    virtual ~AidlHalWrapper() = default;
+    explicit AidlHalWrapper(std::shared_ptr<aidl::android::hardware::power::IPower> handle)
+          : mHandle(std::move(handle)) {}
+    ~AidlHalWrapper() override = default;
 
-    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
-    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
-    virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+                             int32_t durationMs) override;
+    HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<int64_t> getHintSessionPreferredRate() override;
 
 private:
     // Control access to the boost and mode supported arrays.
     std::mutex mBoostMutex;
     std::mutex mModeMutex;
-    sp<hardware::power::IPower> mHandle;
+    std::shared_ptr<aidl::android::hardware::power::IPower> mHandle;
     // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
     // Need to increase the array size if more boost supported.
-    std::array<std::atomic<HalSupport>,
-               static_cast<int32_t>(hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) + 1>
+    std::array<
+            std::atomic<HalSupport>,
+            static_cast<int32_t>(aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) +
+                    1>
             mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN};
     std::array<std::atomic<HalSupport>,
-               static_cast<int32_t>(*(android::enum_range<hardware::power::Mode>().end() - 1)) + 1>
+               static_cast<int32_t>(
+                       *(ndk::enum_range<aidl::android::hardware::power::Mode>().end() - 1)) +
+                       1>
             mModeSupportedArray GUARDED_BY(mModeMutex) = {HalSupport::UNKNOWN};
 };
 
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index aca5009..5264276 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -21,6 +21,7 @@
 #include <binder/ActivityManager.h>
 #include <binder/Binder.h>
 #include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
 
 #include <utils/SystemClock.h>
 
@@ -33,27 +34,36 @@
 sp<IActivityManager> ActivityManager::getService()
 {
     std::lock_guard<Mutex> scoped_lock(mLock);
-    int64_t startTime = 0;
     sp<IActivityManager> service = mService;
-    while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
-        sp<IBinder> binder = defaultServiceManager()->checkService(String16("activity"));
-        if (binder == nullptr) {
-            // Wait for the activity service to come back...
-            if (startTime == 0) {
-                startTime = uptimeMillis();
-                ALOGI("Waiting for activity service");
-            } else if ((uptimeMillis() - startTime) > 1000000) {
-                ALOGW("Waiting too long for activity service, giving up");
-                service = nullptr;
-                break;
-            }
-            usleep(25000);
-        } else {
+    if (ProcessState::self()->isThreadPoolStarted()) {
+        if (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+            sp<IBinder> binder = defaultServiceManager()->waitForService(String16("activity"));
             service = interface_cast<IActivityManager>(binder);
             mService = service;
         }
+    } else {
+        ALOGI("Thread pool not started. Polling for activity service.");
+        int64_t startTime = 0;
+        while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+            sp<IBinder> binder = defaultServiceManager()->checkService(String16("activity"));
+            if (binder == nullptr) {
+                // Wait for the activity service to come back...
+                if (startTime == 0) {
+                    startTime = uptimeMillis();
+                    ALOGI("Waiting for activity service");
+                } else if ((uptimeMillis() - startTime) > 1000000) {
+                    ALOGW("Waiting too long for activity service, giving up");
+                    service = nullptr;
+                    break;
+                }
+                usleep(25000);
+            } else {
+                service = interface_cast<IActivityManager>(binder);
+                mService = service;
+            }
+        }
     }
-    return service;
+    return mService;
 }
 
 int ActivityManager::openContentUri(const String16& stringUri)
diff --git a/libs/binder/MemoryHeapBase.cpp b/libs/binder/MemoryHeapBase.cpp
index 3da06ba..fc273e0 100644
--- a/libs/binder/MemoryHeapBase.cpp
+++ b/libs/binder/MemoryHeapBase.cpp
@@ -73,8 +73,8 @@
         ALOGV("MemoryHeapBase: Attempting to force MemFD");
         fd = memfd_create_region(name ? name : "MemoryHeapBase", size);
         if (fd < 0 || (mapfd(fd, true, size) != NO_ERROR)) return;
-        const int SEAL_FLAGS = ((mFlags & READ_ONLY) ? F_SEAL_FUTURE_WRITE : 0) |
-                ((mFlags & MEMFD_ALLOW_SEALING_FLAG) ? 0 : F_SEAL_SEAL);
+        const int SEAL_FLAGS = ((mFlags & READ_ONLY) ? F_SEAL_FUTURE_WRITE : 0) | F_SEAL_GROW |
+                F_SEAL_SHRINK | ((mFlags & MEMFD_ALLOW_SEALING_FLAG) ? 0 : F_SEAL_SEAL);
         if (SEAL_FLAGS && (fcntl(fd, F_ADD_SEALS, SEAL_FLAGS) == -1)) {
             ALOGE("MemoryHeapBase: MemFD %s sealing with flags %x failed with error  %s", name,
                   SEAL_FLAGS, strerror(errno));
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 17bdc45..a3ff7d2 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -70,6 +70,10 @@
 typedef uintptr_t binder_uintptr_t;
 #endif // BINDER_WITH_KERNEL_IPC
 
+#ifdef __BIONIC__
+#include <android/fdsan.h>
+#endif
+
 #define LOG_REFS(...)
 // #define LOG_REFS(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
 #define LOG_ALLOC(...)
@@ -110,6 +114,37 @@
 // Maximum size of a blob to transfer in-place.
 static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;
 
+#if defined(__BIONIC__)
+static void FdTag(int fd, const void* old_addr, const void* new_addr) {
+    if (android_fdsan_exchange_owner_tag) {
+        uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+                                                          reinterpret_cast<uint64_t>(old_addr));
+        uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+                                                          reinterpret_cast<uint64_t>(new_addr));
+        android_fdsan_exchange_owner_tag(fd, old_tag, new_tag);
+    }
+}
+static void FdTagClose(int fd, const void* addr) {
+    if (android_fdsan_close_with_tag) {
+        uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+                                                      reinterpret_cast<uint64_t>(addr));
+        android_fdsan_close_with_tag(fd, tag);
+    } else {
+        close(fd);
+    }
+}
+#else
+static void FdTag(int fd, const void* old_addr, const void* new_addr) {
+    (void)fd;
+    (void)old_addr;
+    (void)new_addr;
+}
+static void FdTagClose(int fd, const void* addr) {
+    (void)addr;
+    close(fd);
+}
+#endif
+
 enum {
     BLOB_INPLACE = 0,
     BLOB_ASHMEM_IMMUTABLE = 1,
@@ -135,6 +170,9 @@
             return;
         }
         case BINDER_TYPE_FD: {
+            if (obj.cookie != 0) { // owned
+                FdTag(obj.handle, nullptr, who);
+            }
             return;
         }
     }
@@ -160,8 +198,10 @@
             return;
         }
         case BINDER_TYPE_FD: {
+            // note: this path is not used when mOwner, so the tag is also released
+            // in 'closeFileDescriptors'
             if (obj.cookie != 0) { // owned
-                close(obj.handle);
+                FdTagClose(obj.handle, who);
             }
             return;
         }
@@ -555,7 +595,6 @@
                 kernelFields->mObjectsSize++;
 
                 flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData + off);
-                acquire_object(proc, *flat, this);
 
                 if (flat->hdr.type == BINDER_TYPE_FD) {
                     // If this is a file descriptor, we need to dup it so the
@@ -568,6 +607,8 @@
                         err = FDS_NOT_ALLOWED;
                     }
                 }
+
+                acquire_object(proc, *flat, this);
             }
         }
 #else
@@ -2571,7 +2612,8 @@
                     reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]);
             if (flat->hdr.type == BINDER_TYPE_FD) {
                 // ALOGI("Closing fd: %ld", flat->handle);
-                close(flat->handle);
+                // FDs from the kernel are always owned
+                FdTagClose(flat->handle, this);
             }
         }
 #else  // BINDER_WITH_KERNEL_IPC
@@ -2652,6 +2694,10 @@
             kernelFields->mObjectsSize = 0;
             break;
         }
+        if (type == BINDER_TYPE_FD) {
+            // FDs from the kernel are always owned
+            FdTag(flat->handle, 0, this);
+        }
         minOffset = offset + sizeof(flat_binder_object);
     }
     scanForFds();
diff --git a/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp b/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
index 278dd2b..140270f 100644
--- a/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
+++ b/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
@@ -37,7 +37,8 @@
     ASSERT_NE(mHeap.get(), nullptr);
     int fd = mHeap->getHeapID();
     EXPECT_NE(fd, -1);
-    EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_SEAL);
+    EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL);
+    EXPECT_EQ(ftruncate(fd, 4096), -1);
 }
 
 TEST(MemoryHeapBase, MemfdUnsealed) {
@@ -48,7 +49,8 @@
     ASSERT_NE(mHeap.get(), nullptr);
     int fd = mHeap->getHeapID();
     EXPECT_NE(fd, -1);
-    EXPECT_EQ(fcntl(fd, F_GET_SEALS), 0);
+    EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK);
+    EXPECT_EQ(ftruncate(fd, 4096), -1);
 }
 
 TEST(MemoryHeapBase, MemfdSealedProtected) {
@@ -59,7 +61,9 @@
     ASSERT_NE(mHeap.get(), nullptr);
     int fd = mHeap->getHeapID();
     EXPECT_NE(fd, -1);
-    EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_SEAL | F_SEAL_FUTURE_WRITE);
+    EXPECT_EQ(fcntl(fd, F_GET_SEALS),
+              F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL | F_SEAL_FUTURE_WRITE);
+    EXPECT_EQ(ftruncate(fd, 4096), -1);
 }
 
 TEST(MemoryHeapBase, MemfdUnsealedProtected) {
@@ -71,7 +75,8 @@
     ASSERT_NE(mHeap.get(), nullptr);
     int fd = mHeap->getHeapID();
     EXPECT_NE(fd, -1);
-    EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_FUTURE_WRITE);
+    EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_FUTURE_WRITE);
+    EXPECT_EQ(ftruncate(fd, 4096), -1);
 }
 
 #else
diff --git a/libs/bufferstreams/Android.bp b/libs/bufferstreams/Android.bp
new file mode 100644
index 0000000..365fc45
--- /dev/null
+++ b/libs/bufferstreams/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+aconfig_declarations {
+    name: "bufferstreams_flags",
+    package: "com.android.graphics.bufferstreams.flags",
+    srcs: [
+        "aconfig/bufferstreams_flags.aconfig",
+    ],
+}
+
+rust_aconfig_library {
+    name: "libbufferstreams_flags_rust",
+    crate_name: "bufferstreams_flags",
+    aconfig_declarations: "bufferstreams_flags",
+}
+
+cc_aconfig_library {
+    name: "libbufferstreams_flags_cc",
+    aconfig_declarations: "bufferstreams_flags",
+}
diff --git a/libs/bufferstreams/OWNERS b/libs/bufferstreams/OWNERS
new file mode 100644
index 0000000..32b72b8
--- /dev/null
+++ b/libs/bufferstreams/OWNERS
@@ -0,0 +1,7 @@
+carlosmr@google.com
+hibrian@google.com
+jreck@google.com
+jshargo@google.com
+
+file:/services/surfaceflinger/OWNERS
+
diff --git a/libs/bufferstreams/README.md b/libs/bufferstreams/README.md
new file mode 100644
index 0000000..860adef
--- /dev/null
+++ b/libs/bufferstreams/README.md
@@ -0,0 +1,13 @@
+# libbufferstreams: Reactive Streams for Graphics Buffers
+
+This library is currently **experimental** and **under active development**.
+It is not production ready yet.
+
+For more information on reactive streams, please see <https://www.reactive-streams.org/>
+
+## Contributing
+
+This library is natively written in Rust and exposes a C API. If you make changes to the Rust API,
+you **must** update the C API in turn. To do so, with cbindgen installed, run:
+
+```$ ./update_include.sh```
diff --git a/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig
new file mode 100644
index 0000000..e258725
--- /dev/null
+++ b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig
@@ -0,0 +1,65 @@
+package: "com.android.graphics.bufferstreams.flags"
+
+flag {
+  name: "bufferstreams_steel_thread"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams steel thread milestone"
+  bug: "296101122"
+}
+
+flag {
+  name: "bufferstreams_local"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams single-process functionality milestone"
+  bug: "296100790"
+}
+
+flag {
+  name: "bufferstreams_pooling"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams buffer pooling milestone"
+  bug: "296101127"
+}
+
+flag {
+  name: "bufferstreams_ipc"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams IPC milestone"
+  bug: "296099728"
+}
+
+flag {
+  name: "bufferstreams_cpp"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams C/C++ milestone"
+  bug: "296100536"
+}
+
+flag {
+  name: "bufferstreams_utils"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams extra utilities milestone"
+  bug: "285322189"
+}
+
+flag {
+  name: "bufferstreams_demo"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams demo milestone"
+  bug: "297242965"
+}
+
+flag {
+  name: "bufferstreams_perf"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams performance enhancement milestone"
+  bug: "297242843"
+}
+
+flag {
+  name: "bufferstreams_tooling"
+  namespace: "core_graphics"
+  description: "Flag for bufferstreams tooling milestone"
+  bug: "297243180"
+}
+
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
new file mode 100644
index 0000000..0ecf94c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_app {
+    name: "BufferStreamsDemoApp",
+    srcs: ["java/**/*.java"],
+    sdk_version: "current",
+
+    jni_uses_platform_apis: true,
+    jni_libs: ["libbufferstreamdemoapp"],
+    use_embedded_native_libs: true,
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+    ],
+}
diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml
new file mode 100644
index 0000000..872193c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.graphics.bufferstreamsdemoapp"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AppCompat.Light"
+        tools:targetApi="34">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java
new file mode 100644
index 0000000..67b95a5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.android.graphics.bufferstreamsdemoapp;
+
+import android.os.Bundle;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+    // Used to load the 'bufferstreamsdemoapp' library on application startup.
+    static { System.loadLibrary("bufferstreamdemoapp"); }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        RunBufferQueue();
+        System.out.println("stringFromJNI: " + stringFromJNI());
+    }
+
+    /**
+     * A native method that is implemented by the 'bufferstreamsdemoapp' native
+     * library, which is packaged with this application.
+     */
+    public native String stringFromJNI();
+    public native void RunBufferQueue();
+}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/jni/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp
new file mode 100644
index 0000000..67910a1
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_shared {
+    name: "libbufferstreamdemoapp",
+    cflags: [
+        "-Werror",
+        "-Wno-error=unused-parameter",
+    ],
+    shared_libs: [
+        "libgui",
+        "libbase",
+        "libutils",
+    ],
+    header_libs: ["jni_headers"],
+    srcs: ["*.cpp"],
+}
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
new file mode 100644
index 0000000..34e0eb4
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -0,0 +1,37 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <jni.h>
+
+#include <gui/BufferQueue.h>
+
+extern "C"
+{
+    JNIEXPORT jstring JNICALL
+    Java_com_android_graphics_bufferstreamsdemoapp_MainActivity_stringFromJNI(
+            JNIEnv *env,
+            jobject /* this */) {
+        const char* hello = "Hello from C++";
+        return env->NewStringUTF(hello);
+    }
+
+    JNIEXPORT void JNICALL
+    Java_com_android_graphics_bufferstreamsdemoapp_MainActivity_RunBufferQueue(
+            JNIEnv *env,
+            jobject /* this */) {
+        android::sp<android::IGraphicBufferProducer> producer;
+        android::sp<android::IGraphicBufferConsumer> consumer;
+        android::BufferQueue::createBufferQueue(&producer, &consumer);
+    }
+}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/layout/activity_main.xml b/libs/bufferstreams/examples/app/res/layout/activity_main.xml
new file mode 100644
index 0000000..79fb331
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:id="@+id/sample_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        tools:layout_editor_absoluteX="100dp"
+        tools:layout_editor_absoluteY="100dp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/values/colors.xml b/libs/bufferstreams/examples/app/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml
new file mode 100644
index 0000000..e652102
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Buffer Demos</string>
+</resources>
\ No newline at end of file
diff --git a/libs/bufferstreams/include/bufferstreams.h b/libs/bufferstreams/include/bufferstreams.h
new file mode 100644
index 0000000..5308de2
--- /dev/null
+++ b/libs/bufferstreams/include/bufferstreams.h
@@ -0,0 +1,13 @@
+/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+
+/**
+ * This function will print Hello World.
+ */
+bool hello(void);
diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp
new file mode 100644
index 0000000..7fcb222
--- /dev/null
+++ b/libs/bufferstreams/rust/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+rust_defaults {
+    name: "libbufferstreams_defaults",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libnativewindow_rs",
+    ],
+    edition: "2021",
+}
+
+rust_library {
+    name: "libbufferstreams",
+    crate_name: "bufferstreams",
+    defaults: ["libbufferstreams_defaults"],
+    min_sdk_version: "30",
+}
+
+rust_test {
+    name: "libbufferstreams-internal_test",
+    crate_name: "bufferstreams",
+    defaults: ["libbufferstreams_defaults"],
+    test_suites: ["general-tests"],
+}
diff --git a/libs/bufferstreams/rust/Cargo.lock b/libs/bufferstreams/rust/Cargo.lock
new file mode 100644
index 0000000..4482dba
--- /dev/null
+++ b/libs/bufferstreams/rust/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bufferstreams"
+version = "0.1.0"
diff --git a/libs/bufferstreams/rust/Cargo.toml b/libs/bufferstreams/rust/Cargo.toml
new file mode 100644
index 0000000..d30c55c
--- /dev/null
+++ b/libs/bufferstreams/rust/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bufferstreams"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/libs/bufferstreams/rust/cbindgen.toml b/libs/bufferstreams/rust/cbindgen.toml
new file mode 100644
index 0000000..eda837f
--- /dev/null
+++ b/libs/bufferstreams/rust/cbindgen.toml
@@ -0,0 +1,149 @@
+# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
+# for detailed documentation of every option here.
+
+
+
+language = "C"
+
+
+
+############## Options for Wrapping the Contents of the Header #################
+
+# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
+# trailer = "/* Text to put at the end of the generated file */"
+# include_guard = "my_bindings_h"
+# pragma_once = true
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+include_version = false
+# namespace = "my_namespace"
+namespaces = []
+using_namespaces = []
+sys_includes = []
+includes = []
+no_includes = false
+after_includes = ""
+
+
+
+
+############################ Code Style Options ################################
+
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+documentation = true
+documentation_style = "auto"
+documentation_length = "full"
+line_endings = "LF" # also "CR", "CRLF", "Native"
+
+
+
+
+############################# Codegen Options ##################################
+
+style = "both"
+sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
+usize_is_size_t = true
+
+
+
+[defines]
+# "target_os = freebsd" = "DEFINE_FREEBSD"
+# "feature = serde" = "DEFINE_SERDE"
+
+
+
+[export]
+include = []
+exclude = []
+# prefix = "CAPI_"
+item_types = []
+renaming_overrides_prefixing = false
+
+
+
+[export.rename]
+
+
+
+[export.body]
+
+
+[export.mangle]
+
+
+[fn]
+rename_args = "None"
+# must_use = "MUST_USE_FUNC"
+# no_return = "NO_RETURN"
+# prefix = "START_FUNC"
+# postfix = "END_FUNC"
+args = "auto"
+sort_by = "Name"
+
+
+
+
+[struct]
+rename_fields = "None"
+# must_use = "MUST_USE_STRUCT"
+derive_constructor = false
+derive_eq = false
+derive_neq = false
+derive_lt = false
+derive_lte = false
+derive_gt = false
+derive_gte = false
+
+
+
+
+[enum]
+rename_variants = "None"
+# must_use = "MUST_USE_ENUM"
+add_sentinel = false
+prefix_with_name = false
+derive_helper_methods = false
+derive_const_casts = false
+derive_mut_casts = false
+# cast_assert_name = "ASSERT"
+derive_tagged_enum_destructor = false
+derive_tagged_enum_copy_constructor = false
+enum_class = true
+private_default_tagged_enum_constructor = false
+
+
+
+
+[const]
+allow_static_const = true
+allow_constexpr = false
+sort_by = "Name"
+
+
+
+
+[macro_expansion]
+bitflags = false
+
+
+
+
+
+
+############## Options for How Your Rust library Should Be Parsed ##############
+
+[parse]
+parse_deps = false
+# include = []
+exclude = []
+clean = false
+extra_bindings = []
+
+
+
+[parse.expand]
+crates = []
+all_features = false
+default_features = true
+features = []
\ No newline at end of file
diff --git a/libs/bufferstreams/rust/src/buffers/buffer.rs b/libs/bufferstreams/rust/src/buffers/buffer.rs
new file mode 100644
index 0000000..0a8516e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer.rs
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Wrapper around the HardwareBuffer
+
+use nativewindow::*;
+
+use super::{buffer_owner::NoBufferOwner, BufferOwner};
+
+/// A wrapper for a hardware buffer.
+///
+/// This buffer may be associated with a buffer pool to which it will be returned to it when dropped.
+pub struct Buffer {
+    buffer_owner: Box<dyn BufferOwner>,
+    hardware_buffer: HardwareBuffer,
+}
+
+impl Buffer {
+    /// Create new buffer with a custom [BufferOwner].
+    pub fn new(buffer_owner: Box<dyn BufferOwner>, hardware_buffer: HardwareBuffer) -> Self {
+        Self { buffer_owner, hardware_buffer }
+    }
+
+    /// Create a new buffer with no association to any buffer pool.
+    pub fn new_unowned(hardware_buffer: HardwareBuffer) -> Self {
+        Self { buffer_owner: Box::new(NoBufferOwner), hardware_buffer }
+    }
+
+    /// Get the id of the underlying buffer.
+    pub fn id(&self) -> u64 {
+        self.hardware_buffer.id()
+    }
+
+    /// Get a reference to the underlying hardware buffer.
+    pub fn buffer(&self) -> &HardwareBuffer {
+        &self.hardware_buffer
+    }
+}
+
+impl Drop for Buffer {
+    fn drop(&mut self) {
+        self.buffer_owner.on_return(self);
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    use crate::StreamConfig;
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    #[test]
+    fn test_get_buffer_id() {
+        let hardware_buffer = STREAM_CONFIG.create_hardware_buffer().unwrap();
+        let buffer_id = hardware_buffer.id();
+
+        let buffer = Buffer::new_unowned(hardware_buffer);
+        assert_eq!(buffer_id, buffer.id());
+    }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
new file mode 100644
index 0000000..a4abb9d
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::Buffer;
+
+/// Trait that represents an owner of a buffer that might need to handle events such as a buffer
+/// being dropped.
+pub trait BufferOwner {
+    /// Called when a buffer is dropped.
+    fn on_return(&self, buffer: &Buffer);
+}
+
+pub(super) struct NoBufferOwner;
+
+impl BufferOwner for NoBufferOwner {
+    fn on_return(&self, _buffer: &Buffer) {}
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_pool.rs b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
new file mode 100644
index 0000000..05804e2
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
@@ -0,0 +1,137 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! A Buffer Pool containing and managing HardwareBuffers
+
+use std::{
+    collections::HashMap,
+    sync::{Arc, Mutex, Weak},
+};
+
+use nativewindow::*;
+
+use crate::StreamConfig;
+
+use super::{Buffer, BufferOwner};
+
+pub(super) struct BufferPoolInner {
+    size: usize,
+    hardware_buffers: HashMap<u64, HardwareBuffer>,
+    available_buffers: Vec<u64>,
+}
+
+impl BufferPoolInner {
+    pub(super) fn return_buffer(&mut self, buffer_id: u64) {
+        assert!(self.hardware_buffers.contains_key(&buffer_id));
+        assert!(!self.available_buffers.contains(&buffer_id));
+
+        self.available_buffers.push(buffer_id);
+    }
+}
+
+struct BufferPoolOwner(Weak<Mutex<BufferPoolInner>>);
+
+impl BufferOwner for BufferPoolOwner {
+    fn on_return(&self, buffer: &Buffer) {
+        if let Some(locked_buffer_pool) = self.0.upgrade() {
+            let mut buffer_pool = locked_buffer_pool.lock().unwrap();
+
+            buffer_pool.return_buffer(buffer.id());
+        }
+    }
+}
+
+/// A thread-safe collection of buffers.
+///
+/// A buffer pool can be of arbitrary size. It creates and then holds references to all buffers
+/// associated with it.
+pub struct BufferPool(Arc<Mutex<BufferPoolInner>>);
+
+impl BufferPool {
+    /// Creates a new buffer pool of size pool_size. All buffers will be created according to
+    /// the stream config.
+    ///
+    /// This constructor creates all buffers at initialization.
+    pub fn new(pool_size: usize, stream_config: StreamConfig) -> Option<Self> {
+        let mut hardware_buffers = HashMap::new();
+        let mut available_buffers = Vec::new();
+        for _ in 0..pool_size {
+            if let Some(buffer) = stream_config.create_hardware_buffer() {
+                available_buffers.push(buffer.id());
+                hardware_buffers.insert(buffer.id(), buffer);
+            } else {
+                return None;
+            }
+        }
+        Some(Self(Arc::new(Mutex::new(BufferPoolInner {
+            size: pool_size,
+            hardware_buffers,
+            available_buffers,
+        }))))
+    }
+
+    /// Try to acquire the next available buffer in the buffer pool.
+    ///
+    /// If all buffers are in use it will return None.
+    pub fn next_buffer(&mut self) -> Option<Buffer> {
+        let mut inner = self.0.lock().unwrap();
+        if let Some(buffer_id) = inner.available_buffers.pop() {
+            Some(Buffer::new(
+                Box::new(BufferPoolOwner(Arc::downgrade(&self.0))),
+                inner.hardware_buffers[&buffer_id].clone(),
+            ))
+        } else {
+            None
+        }
+    }
+
+    /// Gets the size of the buffer pool.
+    pub fn size(&self) -> usize {
+        let inner = self.0.lock().unwrap();
+        inner.size
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    #[test]
+    fn buffer_pool_next_buffer() {
+        let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+        let next_buffer = buffer_pool.next_buffer();
+
+        assert!(next_buffer.is_some());
+        assert!(buffer_pool.next_buffer().is_none());
+    }
+
+    #[test]
+    fn drop_buffer_returns_to_pool() {
+        let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+        let next_buffer = buffer_pool.next_buffer();
+
+        assert!(next_buffer.is_some());
+        drop(next_buffer);
+        assert!(buffer_pool.next_buffer().is_some());
+    }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/mod.rs b/libs/bufferstreams/rust/src/buffers/mod.rs
new file mode 100644
index 0000000..83360d6
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/mod.rs
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Module containing Buffers and BufferPools
+
+mod buffer;
+mod buffer_owner;
+mod buffer_pool;
+
+pub use buffer::*;
+pub use buffer_owner::*;
+pub use buffer_pool::*;
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
new file mode 100644
index 0000000..be1525d
--- /dev/null
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -0,0 +1,257 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! libbufferstreams: Reactive Streams for Graphics Buffers
+
+pub mod buffers;
+pub mod publishers;
+mod stream_config;
+pub mod subscribers;
+pub mod subscriptions;
+
+use buffers::Buffer;
+pub use stream_config::*;
+
+use std::time::Instant;
+
+/// This function will print Hello World.
+#[no_mangle]
+pub extern "C" fn hello() -> bool {
+    println!("Hello world.");
+    true
+}
+
+/// BufferPublishers provide buffers to BufferSusbscribers. Depending on the
+/// particular object in question, these could be allocated locally or provided
+/// over IPC.
+///
+/// BufferPublishers are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
+/// MUST be less than or equal to the total number of elements requested by that
+/// Subscriber´s Subscription at all times.
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
+/// Subscription by calling on_complete or on_error.
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// MUST be signaled serially.
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
+/// on_complete.
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
+/// that Subscriber’s Subscription MUST be considered cancelled.
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
+/// REQUIRED that no further signals occur.
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
+///  signaled.
+/// * A Publisher MAY support multiple Subscribers and decides whether each
+/// Subscription is unicast or multicast.
+pub trait BufferPublisher {
+    /// Returns the StreamConfig of buffers that publisher creates.
+    fn get_publisher_stream_config(&self) -> StreamConfig;
+    /// This function will create the subscription between the publisher and
+    /// the subscriber.
+    fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static);
+}
+
+/// BufferSubscribers can subscribe to BufferPublishers. They can request Frames
+/// via the BufferSubscription they get from the publisher, then receive Frames
+/// via on_next.
+///
+/// BufferSubcribers are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
+/// MUST be less than or equal to the total number of elements requested by that
+/// Subscriber´s Subscription at all times.
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
+/// Subscription by calling on_complete or on_error.
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// MUST be signaled serially.
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
+/// on_complete.
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
+/// that Subscriber’s Subscription MUST be considered cancelled.
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
+/// REQUIRED that no further signals occur.
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// signaled.
+/// * Publisher.subscribe MAY be called as many times as wanted but MUST be
+/// with a different Subscriber each time.
+/// * A Publisher MAY support multiple Subscribers and decides whether each
+/// Subscription is unicast or multicast.
+pub trait BufferSubscriber {
+    /// The StreamConfig of buffers that this subscriber expects.
+    fn get_subscriber_stream_config(&self) -> StreamConfig;
+    /// This function will be called at the beginning of the subscription.
+    fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>);
+    /// This function will be called for buffer that comes in.
+    fn on_next(&mut self, frame: Frame);
+    /// This function will be called in case of an error.
+    fn on_error(&mut self, error: BufferError);
+    /// This function will be called on finite streams when done.
+    fn on_complete(&mut self);
+}
+
+/// BufferSubscriptions serve as the bridge between BufferPublishers and
+/// BufferSubscribers. BufferSubscribers receive a BufferSubscription when they
+/// subscribe to a BufferPublisher via on_subscribe.
+/// This object is to be used by the BufferSubscriber to cancel its subscription
+/// or request more buffers.
+///
+/// BufferSubcriptions are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * Subscription.request and Subscription.cancel MUST only be called inside
+/// of its Subscriber context.
+/// * The Subscription MUST allow the Subscriber to call Subscription.request
+/// synchronously from within on_next or on_subscribe.
+/// * Subscription.request MUST place an upper bound on possible synchronous
+/// recursion between Publisher and Subscriber.
+/// * Subscription.request SHOULD respect the responsivity of its caller by
+/// returning in a timely manner.
+/// * Subscription.cancel MUST respect the responsivity of its caller by
+/// returning in a timely manner, MUST be idempotent and MUST be thread-safe.
+/// * After the Subscription is cancelled, additional
+/// Subscription.request(n: u64) MUST be NOPs.
+/// * After the Subscription is cancelled, additional Subscription.cancel()
+/// MUST be NOPs.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MUST register the given number of additional elements to be produced to the
+/// respective subscriber.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MUST signal on_error if the argument is <= 0. The cause message SHOULD
+/// explain that non-positive request signals are illegal.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MAY synchronously call on_next on this (or other) subscriber(s).
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MAY synchronously call on_complete or on_error on this (or other)
+/// subscriber(s).
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
+/// request the Publisher to eventually stop signaling its Subscriber. The
+/// operation is NOT REQUIRED to affect the Subscription immediately.
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
+/// request the Publisher to eventually drop any references to the corresponding
+/// subscriber.
+/// * While the Subscription is not cancelled, calling Subscription.cancel MAY
+/// cause the Publisher, if stateful, to transition into the shut-down state if
+/// no other Subscription exists at this point.
+/// * Calling Subscription.cancel MUST return normally.
+/// * Calling Subscription.request MUST return normally.
+pub trait BufferSubscription {
+    /// request
+    fn request(&self, n: u64);
+    /// cancel
+    fn cancel(&self);
+}
+
+/// Type used to describe errors produced by subscriptions.
+pub type BufferError = anyhow::Error;
+
+/// Struct used to contain the buffer.
+pub struct Frame {
+    /// A buffer to be used this frame.
+    pub buffer: Buffer,
+    /// The time at which the buffer was dispatched.
+    pub present_time: Instant,
+    /// A fence used for reading/writing safely.
+    pub fence: i32,
+}
+
+#[cfg(test)]
+mod test {
+    #![allow(warnings, unused)]
+    use super::*;
+
+    use anyhow::anyhow;
+    use buffers::Buffer;
+    use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+    use std::borrow::BorrowMut;
+    use std::error::Error;
+    use std::ops::Add;
+    use std::sync::Arc;
+    use std::time::Duration;
+
+    use crate::publishers::testing::*;
+    use crate::subscribers::{testing::*, SharedSubscriber};
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    fn make_frame() -> Frame {
+        Frame {
+            buffer: Buffer::new_unowned(
+                STREAM_CONFIG
+                    .create_hardware_buffer()
+                    .expect("Unable to create hardware buffer for test"),
+            ),
+            present_time: Instant::now() + Duration::from_secs(1),
+            fence: 0,
+        }
+    }
+
+    #[test]
+    fn test_test_implementations_next() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+        let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+        publisher.subscribe(subscriber.clone());
+        assert!(subscriber.map_inner(|s| s.has_subscription()));
+        assert!(publisher.has_subscriber());
+
+        publisher.send_frame(make_frame());
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(!matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+
+        subscriber.map_inner(|s| s.request(1));
+        assert_eq!(publisher.pending_requests(), 1);
+
+        publisher.send_frame(make_frame());
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+        assert_eq!(publisher.pending_requests(), 0);
+    }
+
+    #[test]
+    fn test_test_implementations_complete() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+        let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+        publisher.subscribe(subscriber.clone());
+        assert!(subscriber.map_inner(|s| s.has_subscription()));
+        assert!(publisher.has_subscriber());
+
+        publisher.send_complete();
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Complete));
+    }
+
+    #[test]
+    fn test_test_implementations_error() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+        let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+        publisher.subscribe(subscriber.clone());
+        assert!(subscriber.map_inner(|s| s.has_subscription()));
+        assert!(publisher.has_subscriber());
+
+        publisher.send_error(anyhow!("error"));
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Error(_)));
+    }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
new file mode 100644
index 0000000..846105d
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//!
+
+use std::time::Instant;
+
+use crate::{
+    buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher,
+    BufferSubscriber, Frame, StreamConfig,
+};
+
+/// The [BufferPoolPublisher] submits buffers from a pool over to the subscriber.
+pub struct BufferPoolPublisher {
+    stream_config: StreamConfig,
+    buffer_pool: BufferPool,
+    subscription: SharedBufferSubscription,
+    subscriber: Option<Box<dyn BufferSubscriber>>,
+}
+
+impl BufferPoolPublisher {
+    /// The [BufferPoolPublisher] needs to initialize a [BufferPool], the [BufferPool] will create
+    /// all buffers at initialization using the stream_config.
+    pub fn new(stream_config: StreamConfig, size: usize) -> Option<Self> {
+        BufferPool::new(size, stream_config).map(|buffer_pool| Self {
+            stream_config,
+            buffer_pool,
+            subscription: SharedBufferSubscription::new(),
+            subscriber: None,
+        })
+    }
+
+    /// If the [SharedBufferSubscription] is ready for a [Frame], a buffer will be requested from
+    /// [BufferPool] and sent over to the [BufferSubscriber].
+    pub fn send_next_frame(&mut self, present_time: Instant) -> bool {
+        if let Some(subscriber) = self.subscriber.as_mut() {
+            if self.subscription.take_request() {
+                if let Some(buffer) = self.buffer_pool.next_buffer() {
+                    let frame = Frame { buffer, present_time, fence: 0 };
+
+                    subscriber.on_next(frame);
+                    return true;
+                }
+            }
+        }
+        false
+    }
+}
+
+impl BufferPublisher for BufferPoolPublisher {
+    fn get_publisher_stream_config(&self) -> StreamConfig {
+        self.stream_config
+    }
+
+    fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+        assert!(self.subscriber.is_none());
+
+        self.subscriber = Some(Box::new(subscriber));
+        self.subscriber.as_mut().unwrap().on_subscribe(self.subscription.clone_for_subscriber());
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+
+    use super::*;
+
+    use crate::{
+        subscribers::{
+            testing::{TestSubscriber, TestingSubscriberEvent},
+            SharedSubscriber,
+        },
+        StreamConfig,
+    };
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    #[test]
+    fn test_send_next_frame() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+
+        let mut buffer_pool_publisher = BufferPoolPublisher::new(STREAM_CONFIG, 1).unwrap();
+        buffer_pool_publisher.subscribe(subscriber.clone());
+
+        subscriber.map_inner(|s| s.request(1));
+
+        assert!(buffer_pool_publisher.send_next_frame(Instant::now()));
+
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+        assert_eq!(buffer_pool_publisher.subscription.pending_requests(), 0);
+    }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs
new file mode 100644
index 0000000..8ed3ba0
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/mod.rs
@@ -0,0 +1,20 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+mod buffer_pool_publisher;
+pub mod testing;
+
+pub use buffer_pool_publisher::*;
diff --git a/libs/bufferstreams/rust/src/publishers/testing.rs b/libs/bufferstreams/rust/src/publishers/testing.rs
new file mode 100644
index 0000000..1593b18
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/testing.rs
@@ -0,0 +1,103 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provides useful publishers for testing specifically. These should not be used in normal code.
+
+use crate::{subscriptions::SharedBufferSubscription, *};
+
+/// A [BufferPublisher] specifically for testing.
+///
+/// Provides users the ability to send events and read the state of the subscription.
+pub struct TestPublisher {
+    config: StreamConfig,
+    subscriber: Option<Box<dyn BufferSubscriber>>,
+    subscription: SharedBufferSubscription,
+}
+
+impl TestPublisher {
+    /// Create a new [TestPublisher].
+    pub fn new(config: StreamConfig) -> Self {
+        Self { config, subscriber: None, subscription: SharedBufferSubscription::new() }
+    }
+
+    /// Send a [BufferSubscriber::on_next] event to an owned [BufferSubscriber] if it has any
+    /// requested and returns true. Drops the frame and returns false otherwise.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscriber.
+    pub fn send_frame(&mut self, frame: Frame) -> bool {
+        let subscriber =
+            self.subscriber.as_deref_mut().expect("Tried to send_frame with no subscriber");
+
+        if self.subscription.take_request() {
+            subscriber.on_next(frame);
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Send a [BufferSubscriber::on_complete] event to an owned [BufferSubscriber].
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscriber.
+    pub fn send_complete(&mut self) {
+        let subscriber =
+            self.subscriber.as_deref_mut().expect("Tried to send_complete with no subscriber");
+        subscriber.on_complete();
+    }
+
+    /// Send a [BufferSubscriber::on_error] event to an owned [BufferSubscriber].
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscriber.
+    pub fn send_error(&mut self, error: BufferError) {
+        let subscriber =
+            self.subscriber.as_deref_mut().expect("Tried to send_error with no subscriber");
+        subscriber.on_error(error);
+    }
+
+    /// Returns whether this [BufferPublisher] owns a subscriber.
+    pub fn has_subscriber(&self) -> bool {
+        self.subscriber.is_some()
+    }
+
+    /// Returns the nummber of frames requested by the [BufferSubscriber].
+    pub fn pending_requests(&self) -> u64 {
+        self.subscription.pending_requests()
+    }
+
+    /// Returns whether the [BufferSubscriber] has cancelled the subscription.
+    pub fn is_cancelled(&self) -> bool {
+        self.subscription.is_cancelled()
+    }
+}
+
+impl BufferPublisher for TestPublisher {
+    fn get_publisher_stream_config(&self) -> crate::StreamConfig {
+        self.config
+    }
+
+    fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+        assert!(self.subscriber.is_none(), "TestingPublishers can only take one subscriber");
+        self.subscriber = Some(Box::new(subscriber));
+
+        if let Some(ref mut subscriber) = self.subscriber {
+            subscriber.on_subscribe(self.subscription.clone_for_subscriber());
+        }
+    }
+}
diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs
new file mode 100644
index 0000000..454bdf1
--- /dev/null
+++ b/libs/bufferstreams/rust/src/stream_config.rs
@@ -0,0 +1,67 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use nativewindow::*;
+
+/// The configuration of the buffers published by a [BufferPublisher] or
+/// expected by a [BufferSubscriber].
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct StreamConfig {
+    /// Width in pixels of streaming buffers.
+    pub width: u32,
+    /// Height in pixels of streaming buffers.
+    pub height: u32,
+    /// Number of layers of streaming buffers.
+    pub layers: u32,
+    /// Format of streaming buffers.
+    pub format: AHardwareBuffer_Format::Type,
+    /// Usage of streaming buffers.
+    pub usage: AHardwareBuffer_UsageFlags,
+    /// Stride of streaming buffers.
+    pub stride: u32,
+}
+
+impl StreamConfig {
+    /// Tries to create a new HardwareBuffer from settings in a [StreamConfig].
+    pub fn create_hardware_buffer(&self) -> Option<HardwareBuffer> {
+        HardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_create_hardware_buffer() {
+        let config = StreamConfig {
+            width: 123,
+            height: 456,
+            layers: 1,
+            format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN
+                | AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            stride: 0,
+        };
+
+        let maybe_buffer = config.create_hardware_buffer();
+        assert!(maybe_buffer.is_some());
+
+        let buffer = maybe_buffer.unwrap();
+        assert_eq!(config.width, buffer.width());
+        assert_eq!(config.height, buffer.height());
+        assert_eq!(config.format, buffer.format());
+        assert_eq!(config.usage, buffer.usage());
+    }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/mod.rs b/libs/bufferstreams/rust/src/subscribers/mod.rs
new file mode 100644
index 0000000..dd038c6
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/mod.rs
@@ -0,0 +1,20 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+mod shared;
+pub mod testing;
+
+pub use shared::*;
diff --git a/libs/bufferstreams/rust/src/subscribers/shared.rs b/libs/bufferstreams/rust/src/subscribers/shared.rs
new file mode 100644
index 0000000..46c58dc
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/shared.rs
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A [BufferSubscriber] wrapper that provides shared access.
+///
+/// Normally, [BufferSubscriber]s are fully owned by the publisher that they are attached to. With
+/// [SharedSubscriber], a
+///
+/// # Panics
+///
+/// [BufferSubscriber::on_subscribe] on a [SharedSubscriber] can only be called once, otherwise it
+/// will panic. This is to prevent accidental and unsupported sharing between multiple publishers to
+/// reflect the usual behavior where a publisher takes full ownership of a subscriber.
+pub struct SharedSubscriber<S: BufferSubscriber>(Arc<Mutex<SharedSubscriberInner<S>>>);
+
+struct SharedSubscriberInner<S: BufferSubscriber> {
+    subscriber: S,
+    is_subscribed: bool,
+}
+
+impl<S: BufferSubscriber> SharedSubscriber<S> {
+    /// Create a new wrapper around a [BufferSubscriber].
+    pub fn new(subscriber: S) -> Self {
+        Self(Arc::new(Mutex::new(SharedSubscriberInner { subscriber, is_subscribed: false })))
+    }
+
+    /// Provides access to an immutable reference to the wrapped [BufferSubscriber].
+    pub fn map_inner<R, F: FnOnce(&S) -> R>(&self, f: F) -> R {
+        let inner = self.0.lock().unwrap();
+        f(&inner.subscriber)
+    }
+
+    /// Provides access to a mutable reference to the wrapped [BufferSubscriber].
+    pub fn map_inner_mut<R, F: FnOnce(&mut S) -> R>(&self, f: F) -> R {
+        let mut inner = self.0.lock().unwrap();
+        f(&mut inner.subscriber)
+    }
+}
+
+impl<S: BufferSubscriber> Clone for SharedSubscriber<S> {
+    fn clone(&self) -> Self {
+        Self(Arc::clone(&self.0))
+    }
+}
+
+impl<S: BufferSubscriber> BufferSubscriber for SharedSubscriber<S> {
+    fn get_subscriber_stream_config(&self) -> StreamConfig {
+        let inner = self.0.lock().unwrap();
+        inner.subscriber.get_subscriber_stream_config()
+    }
+
+    fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+        let mut inner = self.0.lock().unwrap();
+        assert!(
+            !inner.is_subscribed,
+            "A SharedSubscriber can not be shared between two BufferPublishers"
+        );
+        inner.is_subscribed = true;
+
+        inner.subscriber.on_subscribe(subscription);
+    }
+
+    fn on_next(&mut self, frame: Frame) {
+        let mut inner = self.0.lock().unwrap();
+        inner.subscriber.on_next(frame);
+    }
+
+    fn on_error(&mut self, error: BufferError) {
+        let mut inner = self.0.lock().unwrap();
+        inner.subscriber.on_error(error);
+    }
+
+    fn on_complete(&mut self) {
+        let mut inner = self.0.lock().unwrap();
+        inner.subscriber.on_complete();
+    }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/testing.rs b/libs/bufferstreams/rust/src/subscribers/testing.rs
new file mode 100644
index 0000000..b7e9705
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/testing.rs
@@ -0,0 +1,106 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provides useful subscribers for testing specifically. These should not be used in normal code.
+
+use crate::*;
+
+/// Represents a callback called by a [BufferPublisher] on a [BufferSubscriber].
+pub enum TestingSubscriberEvent {
+    /// Represents a call to [BufferSubscriber::on_subscribe].
+    Subscribe,
+    /// Represents a call to [BufferSubscriber::on_next].
+    Next(Frame),
+    /// Represents a call to [BufferSubscriber::on_error].
+    Error(BufferError),
+    /// Represents a call to [BufferSubscriber::on_complete].
+    Complete,
+}
+
+/// A [BufferSubscriber] specifically for testing. Logs events as they happen which can be retrieved
+/// by the test to ensure appropriate behavior.
+pub struct TestSubscriber {
+    config: StreamConfig,
+    subscription: Option<Box<dyn BufferSubscription>>,
+    events: Vec<TestingSubscriberEvent>,
+}
+
+impl TestSubscriber {
+    /// Create a new [TestSubscriber].
+    pub fn new(config: StreamConfig) -> Self {
+        Self { config, subscription: None, events: Vec::new() }
+    }
+
+    /// Returns true if this [BufferSubscriber] has an active subscription.
+    pub fn has_subscription(&self) -> bool {
+        self.subscription.is_some()
+    }
+
+    /// Make a request on behalf of this test subscriber.
+    ///
+    /// This will panic if there is no owned subscription.
+    pub fn request(&self, n: u64) {
+        let subscription = self
+            .subscription
+            .as_deref()
+            .expect("Tried to request on a TestSubscriber with no subscription");
+        subscription.request(n);
+    }
+
+    /// Cancel on behalf of this test subscriber.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscription.
+    pub fn cancel(&self) {
+        let subscription = self
+            .subscription
+            .as_deref()
+            .expect("Tried to cancel a TestSubscriber with no subscription");
+        subscription.cancel();
+    }
+
+    /// Gets all of the events that have happened to this [BufferSubscriber] since the last call
+    /// to this function or it was created.
+    pub fn take_events(&mut self) -> Vec<TestingSubscriberEvent> {
+        let mut out = Vec::new();
+        out.append(&mut self.events);
+        out
+    }
+}
+
+impl BufferSubscriber for TestSubscriber {
+    fn get_subscriber_stream_config(&self) -> StreamConfig {
+        self.config
+    }
+
+    fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+        assert!(self.subscription.is_none(), "TestSubscriber must only be subscribed to once");
+        self.subscription = Some(subscription);
+
+        self.events.push(TestingSubscriberEvent::Subscribe);
+    }
+
+    fn on_next(&mut self, frame: Frame) {
+        self.events.push(TestingSubscriberEvent::Next(frame));
+    }
+
+    fn on_error(&mut self, error: BufferError) {
+        self.events.push(TestingSubscriberEvent::Error(error));
+    }
+
+    fn on_complete(&mut self) {
+        self.events.push(TestingSubscriberEvent::Complete);
+    }
+}
diff --git a/libs/bufferstreams/rust/src/subscriptions/mod.rs b/libs/bufferstreams/rust/src/subscriptions/mod.rs
new file mode 100644
index 0000000..e046dbb
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/mod.rs
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module provides [BufferSubscription] implementations and helpers.
+
+mod shared_buffer_subscription;
+
+pub use shared_buffer_subscription::*;
diff --git a/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
new file mode 100644
index 0000000..90275c7
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
@@ -0,0 +1,84 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A simple sharable helper that can be used as a [BufferSubscription] by a [BufferSubscriber] and
+/// as a state tracker by a [BufferPublisher].
+#[derive(Clone, Debug)]
+pub struct SharedBufferSubscription(Arc<Mutex<BufferSubscriptionData>>);
+
+#[derive(Debug, Default)]
+struct BufferSubscriptionData {
+    requests: u64,
+    is_cancelled: bool,
+}
+
+impl SharedBufferSubscription {
+    /// Create a new [SharedBufferSubscription].
+    pub fn new() -> Self {
+        SharedBufferSubscription::default()
+    }
+
+    /// Clone this [SharedBufferSubscription] so it can be passed into
+    /// [BufferSubscriber::on_subscribe].
+    pub fn clone_for_subscriber(&self) -> Box<dyn BufferSubscription> {
+        Box::new(self.clone()) as Box<dyn BufferSubscription>
+    }
+
+    /// If possible (not cancelled and with requests pending), take
+    pub fn take_request(&self) -> bool {
+        let mut data = self.0.lock().unwrap();
+
+        if data.is_cancelled || data.requests == 0 {
+            false
+        } else {
+            data.requests -= 1;
+            true
+        }
+    }
+
+    /// Get the number of pending requests made by the [BufferSubscriber] via
+    /// [BufferSubscription::request].
+    pub fn pending_requests(&self) -> u64 {
+        self.0.lock().unwrap().requests
+    }
+
+    /// Get get whether the [BufferSubscriber] has called [BufferSubscription::cancel].
+    pub fn is_cancelled(&self) -> bool {
+        self.0.lock().unwrap().is_cancelled
+    }
+}
+
+impl Default for SharedBufferSubscription {
+    fn default() -> Self {
+        Self(Arc::new(Mutex::new(BufferSubscriptionData::default())))
+    }
+}
+
+impl BufferSubscription for SharedBufferSubscription {
+    fn request(&self, n: u64) {
+        let mut data = self.0.lock().unwrap();
+        if !data.is_cancelled {
+            data.requests = data.requests.saturating_add(n);
+        }
+    }
+
+    fn cancel(&self) {
+        let mut data = self.0.lock().unwrap();
+        data.is_cancelled = true;
+    }
+}
diff --git a/libs/bufferstreams/update_include.sh b/libs/bufferstreams/update_include.sh
new file mode 100755
index 0000000..e986e9f
--- /dev/null
+++ b/libs/bufferstreams/update_include.sh
@@ -0,0 +1,2 @@
+cd rust
+cbindgen --config cbindgen.toml --crate bufferstreams --output ../include/bufferstreams.h
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index 5592a01..b68c2c3 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@
 static_assert(ftl::enum_name(E::C).value_or("?") == "C");
 static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
 
+static_assert(ftl::enum_name_full<E::B>() == "E::B");
+static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F");
+static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C");
+static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?");
+
 enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
 
 static_assert(ftl::enum_begin_v<F> == F{0});
@@ -60,6 +65,10 @@
 static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
 static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
 
+static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone");
+static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4");
+static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7");
+
 // Though not flags, the enumerators are within the implicit range of bit indices.
 enum class Planet : std::uint8_t {
   kMercury,
@@ -81,6 +90,9 @@
 static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
 static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
 
+static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury");
+static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn");
+
 // Unscoped enum must define explicit range, even if the underlying type is fixed.
 enum Temperature : int {
   kRoom = 20,
@@ -122,16 +134,28 @@
     EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
     EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
 
+    EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth");
+    EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune");
+
     EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt);
   }
   {
     EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
     EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
     EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
 
+    EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+    EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+    EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt);
   }
 }
 
@@ -158,16 +182,30 @@
     EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
     EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
 
+    EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth");
+    EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune");
+
     EXPECT_EQ(ftl::enum_string(kPluto), "8");
+
+    EXPECT_EQ(ftl::enum_string_full(kPluto), "8");
+
   }
   {
     EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
     EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
     EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
 
+    EXPECT_EQ(ftl::enum_string_full(kRoom), "20");
+    EXPECT_EQ(ftl::enum_string_full(kFridge), "4");
+    EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18");
+
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30");
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0");
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100");
   }
 }
 
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 6d1dfe8..aab1276 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -58,7 +58,7 @@
     ],
 
     export_shared_lib_headers: [
-        "android.hardware.graphics.common-V4-ndk",
+        "android.hardware.graphics.common-V5-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
     ],
diff --git a/libs/gralloc/types/Gralloc4.cpp b/libs/gralloc/types/Gralloc4.cpp
index 61e6657..ce35906 100644
--- a/libs/gralloc/types/Gralloc4.cpp
+++ b/libs/gralloc/types/Gralloc4.cpp
@@ -1307,6 +1307,10 @@
             return "SitedInterstitial";
         case ChromaSiting::COSITED_HORIZONTAL:
             return "CositedHorizontal";
+        case ChromaSiting::COSITED_VERTICAL:
+            return "CositedVertical";
+        case ChromaSiting::COSITED_BOTH:
+            return "CositedBoth";
     }
 }
 
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index 4c070ae..5dc195c 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -64,9 +64,17 @@
     void setTargetStatsArray(const std::string& appPackageName, const uint64_t driverVersionCode,
                              const GpuStatsInfo::Stats stats, const uint64_t* values,
                              const uint32_t valueCount) override {
-        for (uint32_t i = 0; i < valueCount; i++) {
-            setTargetStats(appPackageName, driverVersionCode, stats, values[i]);
-        }
+        Parcel data, reply;
+        data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+
+        data.writeUtf8AsUtf16(appPackageName);
+        data.writeUint64(driverVersionCode);
+        data.writeInt32(static_cast<int32_t>(stats));
+        data.writeUint32(valueCount);
+        data.write(values, valueCount * sizeof(uint64_t));
+
+        remote()->transact(BnGpuService::SET_TARGET_STATS_ARRAY, data, &reply,
+                           IBinder::FLAG_ONEWAY);
     }
 
     void setUpdatableDriverPath(const std::string& driverPath) override {
@@ -164,6 +172,31 @@
 
             return OK;
         }
+        case SET_TARGET_STATS_ARRAY: {
+            CHECK_INTERFACE(IGpuService, data, reply);
+
+            std::string appPackageName;
+            if ((status = data.readUtf8FromUtf16(&appPackageName)) != OK) return status;
+
+            uint64_t driverVersionCode;
+            if ((status = data.readUint64(&driverVersionCode)) != OK) return status;
+
+            int32_t stats;
+            if ((status = data.readInt32(&stats)) != OK) return status;
+
+            uint32_t valueCount;
+            if ((status = data.readUint32(&valueCount)) != OK) return status;
+
+            std::vector<uint64_t> values(valueCount);
+            if ((status = data.read(values.data(), valueCount * sizeof(uint64_t))) != OK) {
+                return status;
+            }
+
+            setTargetStatsArray(appPackageName, driverVersionCode,
+                                static_cast<GpuStatsInfo::Stats>(stats), values.data(), valueCount);
+
+            return OK;
+        }
         case SET_UPDATABLE_DRIVER_PATH: {
             CHECK_INTERFACE(IGpuService, data, reply);
 
@@ -180,9 +213,9 @@
             return reply->writeUtf8AsUtf16(driverPath);
         }
         case SHELL_COMMAND_TRANSACTION: {
-            int in = data.readFileDescriptor();
-            int out = data.readFileDescriptor();
-            int err = data.readFileDescriptor();
+            int in = dup(data.readFileDescriptor());
+            int out = dup(data.readFileDescriptor());
+            int err = dup(data.readFileDescriptor());
 
             std::vector<String16> args;
             data.readString16Vector(&args);
@@ -195,6 +228,9 @@
 
             status = shellCommand(in, out, err, args);
             if (resultReceiver != nullptr) resultReceiver->send(status);
+            ::close(in);
+            ::close(out);
+            ::close(err);
 
             return OK;
         }
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index e3857d2..45f05d6 100644
--- a/libs/graphicsenv/include/graphicsenv/IGpuService.h
+++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h
@@ -63,6 +63,7 @@
         SET_UPDATABLE_DRIVER_PATH,
         GET_UPDATABLE_DRIVER_PATH,
         TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
+        SET_TARGET_STATS_ARRAY,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 298838d..f17a654 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -192,26 +192,21 @@
     },
 }
 
-cc_library_shared {
-    name: "libgui",
+aconfig_declarations {
+    name: "libgui_flags",
+    package: "com.android.graphics.libgui.flags",
+    srcs: ["libgui_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libguiflags",
+    host_supported: true,
     vendor_available: true,
-    vndk: {
-        enabled: true,
-        private: true,
-    },
-    double_loadable: true,
+    aconfig_declarations: "libgui_flags",
+}
 
-    defaults: ["libgui_bufferqueue-defaults"],
-
-    static_libs: [
-        "libgui_aidl_static",
-        "libgui_window_info_static",
-    ],
-    export_static_lib_headers: [
-        "libgui_aidl_static",
-        "libgui_window_info_static",
-    ],
-
+filegroup {
+    name: "libgui-sources",
     srcs: [
         ":framework_native_aidl_binder",
         ":framework_native_aidl_gui",
@@ -255,11 +250,40 @@
         "bufferqueue/2.0/B2HProducerListener.cpp",
         "bufferqueue/2.0/H2BGraphicBufferProducer.cpp",
     ],
+}
 
+cc_defaults {
+    name: "libgui-defaults",
+    defaults: ["libgui_bufferqueue-defaults"],
+    srcs: [":libgui-sources"],
+    static_libs: [
+        "libgui_aidl_static",
+        "libgui_window_info_static",
+        "libguiflags",
+    ],
     shared_libs: [
         "libbinder",
         "libGLESv2",
     ],
+}
+
+cc_library_shared {
+    name: "libgui",
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+        private: true,
+    },
+    double_loadable: true,
+
+    defaults: [
+        "libgui-defaults",
+    ],
+
+    export_static_lib_headers: [
+        "libgui_aidl_static",
+        "libgui_window_info_static",
+    ],
 
     export_shared_lib_headers: [
         "libbinder",
@@ -333,6 +357,7 @@
         "BufferQueueProducer.cpp",
         "BufferQueueThreadState.cpp",
         "BufferSlot.cpp",
+        "FrameRateUtils.cpp",
         "FrameTimestamps.cpp",
         "GLConsumerUtils.cpp",
         "HdrMetadata.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 207fa4f..dd0a028 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -26,6 +26,8 @@
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <gui/Flags.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
@@ -39,6 +41,9 @@
 #include <android-base/thread_annotations.h>
 #include <chrono>
 
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
 using namespace std::chrono_literals;
 
 namespace {
@@ -139,6 +144,16 @@
     }
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility,
+                                             int8_t changeFrameRateStrategy) {
+    sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+    if (bbq != nullptr) {
+        bbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+}
+#endif
+
 void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) {
     Mutex::Autolock lock(mMutex);
     mFrameEventHistory.resize(newSize);
@@ -890,6 +905,10 @@
 
     status_t setFrameRate(float frameRate, int8_t compatibility,
                           int8_t changeFrameRateStrategy) override {
+        if (flags::bq_setframerate()) {
+            return Surface::setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+        }
+
         std::lock_guard _lock{mMutex};
         if (mDestroyed) {
             return DEAD_OBJECT;
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index 66cad03..ab0f6d2 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -22,6 +22,7 @@
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <gui/Flags.h>
 
 namespace android {
 
@@ -98,6 +99,16 @@
     }
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t compatibility,
+                                                        int8_t changeFrameRateStrategy) {
+    sp<ConsumerListener> listener(mConsumerListener.promote());
+    if (listener != nullptr) {
+        listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+}
+#endif
+
 void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
         sp<IGraphicBufferConsumer>* outConsumer,
         bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 920b83d..67dff6d 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -32,6 +32,8 @@
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <gui/Flags.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
 #include <gui/IConsumerListener.h>
 #include <gui/IProducerListener.h>
@@ -1751,4 +1753,27 @@
     return NO_ERROR;
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+status_t BufferQueueProducer::setFrameRate(float frameRate, int8_t compatibility,
+                                           int8_t changeFrameRateStrategy) {
+    ATRACE_CALL();
+    BQ_LOGV("setFrameRate: %.2f", frameRate);
+
+    if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+                           "BufferQueueProducer::setFrameRate")) {
+        return BAD_VALUE;
+    }
+
+    sp<IConsumerListener> listener;
+    {
+        std::lock_guard<std::mutex> lock(mCore->mMutex);
+        listener = mCore->mConsumerListener;
+    }
+    if (listener != nullptr) {
+        listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+    return NO_ERROR;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 46fb068..93df124 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -324,6 +324,12 @@
           to_string(displayId).c_str(), toString(connected));
 }
 
+void Choreographer::dispatchHotplugConnectionError(nsecs_t, int32_t connectionError) {
+    ALOGV("choreographer %p ~ received hotplug connection error event (connectionError=%d), "
+          "ignoring.",
+          this, connectionError);
+}
+
 void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) {
     LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered");
 }
@@ -394,4 +400,4 @@
     return iter->second;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 8a88377..5dd058c 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -173,7 +173,13 @@
                     *outVsyncEventData = ev.vsync.vsyncData;
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
-                    dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
+                    if (ev.hotplug.connectionError == 0) {
+                        dispatchHotplug(ev.header.timestamp, ev.header.displayId,
+                                        ev.hotplug.connected);
+                    } else {
+                        dispatchHotplugConnectionError(ev.header.timestamp,
+                                                       ev.hotplug.connectionError);
+                    }
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
                     dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index 6849a95..67cbc7b 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -99,7 +99,7 @@
     if (mEventConnection != nullptr) {
         auto status = mEventConnection->getLatestVsyncEventData(outVsyncEventData);
         if (!status.isOk()) {
-            ALOGE("Failed to get latest vsync event data: %s", status.exceptionMessage().c_str());
+            ALOGE("Failed to get latest vsync event data: %s", status.toString8().c_str());
             return status.transactionError();
         }
         return NO_ERROR;
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
new file mode 100644
index 0000000..6993bfa
--- /dev/null
+++ b/libs/gui/FrameRateUtils.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gui/Flags.h>
+#include <gui/FrameRateUtils.h>
+#include <system/window.h>
+#include <utils/Log.h>
+
+#include <cmath>
+
+namespace android {
+// Returns true if the frameRate is valid.
+//
+// @param frameRate the frame rate in Hz
+// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
+// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
+// @param functionName calling function or nullptr. Used for logging
+// @param privileged whether caller has unscoped surfaceflinger access
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+                       const char* inFunctionName, bool privileged) {
+    const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
+    int floatClassification = std::fpclassify(frameRate);
+    if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
+        ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
+        return false;
+    }
+
+    if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
+        (!privileged ||
+         (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
+          compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
+        ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
+              compatibility, privileged ? "yes" : "no");
+        return false;
+    }
+
+    if (__builtin_available(android 31, *)) {
+        if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
+            changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
+            ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
+                  changeFrameRateStrategy);
+            if (FLAG_BQ_SET_FRAME_RATE) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+} // namespace android
diff --git a/libs/gui/FrameTimestamps.cpp b/libs/gui/FrameTimestamps.cpp
index f3eb4e8..afb09de 100644
--- a/libs/gui/FrameTimestamps.cpp
+++ b/libs/gui/FrameTimestamps.cpp
@@ -255,7 +255,6 @@
         uint64_t frameNumber, std::shared_ptr<FenceTime>&& acquire) {
     FrameEvents* frame = getFrame(frameNumber, &mAcquireOffset);
     if (frame == nullptr) {
-        ALOGE("updateAcquireFence: Did not find frame.");
         return;
     }
 
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 918ff2d..d0c09e4 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -27,11 +27,12 @@
 #include <binder/Parcel.h>
 #include <binder/IInterface.h>
 
-#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
-#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
 #include <gui/BufferQueueDefs.h>
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IProducerListener.h>
+#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
 
 namespace android {
 // ----------------------------------------------------------------------------
@@ -78,6 +79,7 @@
     CANCEL_BUFFERS,
     QUERY_MULTIPLE,
     GET_LAST_QUEUED_BUFFER2,
+    SET_FRAME_RATE,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -761,6 +763,21 @@
         }
         return result;
     }
+#if FLAG_BQ_SET_FRAME_RATE
+    virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+                                  int8_t changeFrameRateStrategy) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeFloat(frameRate);
+        data.writeInt32(compatibility);
+        data.writeInt32(changeFrameRateStrategy);
+        status_t result = remote()->transact(SET_FRAME_RATE, data, &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
+#endif
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -956,6 +973,14 @@
     return INVALID_OPERATION;
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+status_t IGraphicBufferProducer::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+                                              int8_t /*changeFrameRateStrategy*/) {
+    // No-op for IGBP other than BufferQueue.
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
     status_t res = OK;
     res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1497,6 +1522,17 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+#if FLAG_BQ_SET_FRAME_RATE
+        case SET_FRAME_RATE: {
+            CHECK_INTERFACE(IGraphicBuffer, data, reply);
+            float frameRate = data.readFloat();
+            int8_t compatibility = data.readInt32();
+            int8_t changeFrameRateStrategy = data.readInt32();
+            status_t result = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index b526a6c..ff6b558 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -25,6 +25,7 @@
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerState.h>
+#include <gui/SchedulingPolicy.h>
 #include <private/gui/ParcelUtils.h>
 #include <stdint.h>
 #include <sys/types.h>
@@ -201,6 +202,18 @@
                                        isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
                                        listenerCallbacks, transactionId, mergedTransactions);
         }
+        case GET_SCHEDULING_POLICY: {
+            gui::SchedulingPolicy policy;
+            const auto status = gui::getSchedulingPolicy(&policy);
+            if (!status.isOk()) {
+                return status.exceptionCode();
+            }
+
+            SAFE_PARCEL(reply->writeInt32, policy.policy);
+            SAFE_PARCEL(reply->writeInt32, policy.priority);
+            return NO_ERROR;
+        }
+
         default: {
             return BBinder::onTransact(code, data, reply, flags);
         }
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index ffe79a3..29d64af 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -111,12 +111,14 @@
 status_t JankData::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeInt64, frameVsyncId);
     SAFE_PARCEL(output->writeInt32, jankType);
+    SAFE_PARCEL(output->writeInt64, frameIntervalNs);
     return NO_ERROR;
 }
 
 status_t JankData::readFromParcel(const Parcel* input) {
     SAFE_PARCEL(input->readInt64, &frameVsyncId);
     SAFE_PARCEL(input->readInt32, &jankType);
+    SAFE_PARCEL(input->readInt64, &frameIntervalNs);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 2322b70..fd8fc8d 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -22,6 +22,7 @@
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/native_window.h>
 #include <binder/Parcel.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/LayerState.h>
 #include <gui/SurfaceControl.h>
@@ -83,6 +84,9 @@
         frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
         changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
         defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
+        frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+        frameRateCategorySmoothSwitchOnly(false),
+        frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
         isTrustedOverlay(false),
@@ -158,6 +162,9 @@
     SAFE_PARCEL(output.writeByte, frameRateCompatibility);
     SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility);
+    SAFE_PARCEL(output.writeByte, frameRateCategory);
+    SAFE_PARCEL(output.writeBool, frameRateCategorySmoothSwitchOnly);
+    SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeBool, autoRefresh);
     SAFE_PARCEL(output.writeBool, dimmingEnabled);
@@ -290,6 +297,9 @@
     SAFE_PARCEL(input.readByte, &frameRateCompatibility);
     SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
     SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility);
+    SAFE_PARCEL(input.readByte, &frameRateCategory);
+    SAFE_PARCEL(input.readBool, &frameRateCategorySmoothSwitchOnly);
+    SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
     SAFE_PARCEL(input.readBool, &autoRefresh);
@@ -659,6 +669,15 @@
         frameRateCompatibility = other.frameRateCompatibility;
         changeFrameRateStrategy = other.changeFrameRateStrategy;
     }
+    if (other.what & eFrameRateCategoryChanged) {
+        what |= eFrameRateCategoryChanged;
+        frameRateCategory = other.frameRateCategory;
+        frameRateCategorySmoothSwitchOnly = other.frameRateCategorySmoothSwitchOnly;
+    }
+    if (other.what & eFrameRateSelectionStrategyChanged) {
+        what |= eFrameRateSelectionStrategyChanged;
+        frameRateSelectionStrategy = other.frameRateSelectionStrategy;
+    }
     if (other.what & eFixedTransformHintChanged) {
         what |= eFixedTransformHintChanged;
         fixedTransformHint = other.fixedTransformHint;
@@ -769,6 +788,9 @@
     CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
                 changeFrameRateStrategy);
+    CHECK_DIFF2(diff, eFrameRateCategoryChanged, other, frameRateCategory,
+                frameRateCategorySmoothSwitchOnly);
+    CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
     CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
     CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
@@ -855,34 +877,6 @@
     return NO_ERROR;
 }
 
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
-                       const char* inFunctionName, bool privileged) {
-    const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
-    int floatClassification = std::fpclassify(frameRate);
-    if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
-        ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
-        return false;
-    }
-
-    if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
-        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        (!privileged ||
-         (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
-          compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
-        ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
-              compatibility, privileged ? "yes" : "no");
-        return false;
-    }
-
-    if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
-        changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
-        ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
-              changeFrameRateStrategy);
-    }
-
-    return true;
-}
-
 // ----------------------------------------------------------------------------
 
 namespace gui {
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 53a2f64..a87f053 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -43,6 +43,7 @@
 
 #include <gui/AidlStatusUtil.h>
 #include <gui/BufferItem.h>
+#include <gui/Flags.h>
 #include <gui/IProducerListener.h>
 
 #include <gui/ISurfaceComposer.h>
@@ -50,8 +51,11 @@
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
+using namespace com::android::graphics::libgui;
 using gui::aidl_utils::statusTFromBinderStatus;
 using ui::Dataspace;
 
@@ -2565,8 +2569,22 @@
     mSurfaceListener->onBuffersDiscarded(discardedBufs);
 }
 
-[[deprecated]] status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
-                                              int8_t /*changeFrameRateStrategy*/) {
+status_t Surface::setFrameRate(float frameRate, int8_t compatibility,
+                               int8_t changeFrameRateStrategy) {
+#if FLAG_BQ_SET_FRAME_RATE
+    if (flags::bq_setframerate()) {
+        status_t err = mGraphicBufferProducer->setFrameRate(frameRate, compatibility,
+                                                            changeFrameRateStrategy);
+        ALOGE_IF(err, "IGraphicBufferProducer::setFrameRate(%.2f) returned %s", frameRate,
+                 strerror(-err));
+        return err;
+    }
+#else
+    static_cast<void>(frameRate);
+    static_cast<void>(compatibility);
+    static_cast<void>(changeFrameRateStrategy);
+#endif
+
     ALOGI("Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not "
           "SurfaceFlinger");
     // ISurfaceComposer no longer supports setFrameRate, we will return NO_ERROR when the api is
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 00495ee..05e2ddf 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -26,6 +26,7 @@
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/TrustedPresentationThresholds.h>
 #include <android/os/IInputConstants.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/TraceUtils.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
@@ -377,7 +378,6 @@
             }
             auto& [callbackFunction, callbackSurfaceControls] = callbacksMap[callbackId];
             if (!callbackFunction) {
-                ALOGE("cannot call null callback function, skipping");
                 continue;
             }
             std::vector<SurfaceControlStats> surfaceControlStats;
@@ -394,6 +394,11 @@
 
             callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
                              surfaceControlStats);
+
+            // More than one transaction may contain the same callback id. Erase the callback from
+            // the map to ensure that it is only called once. This can happen if transactions are
+            // parcelled out of process and applied in both processes.
+            callbacksMap.erase(callbackId);
         }
 
         // handle on complete callbacks
@@ -446,7 +451,9 @@
             callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
                              surfaceControlStats);
         }
+    }
 
+    for (const auto& transactionStats : listenerStats.transactionStats) {
         for (const auto& surfaceStats : transactionStats.surfaceStats) {
             // The callbackMap contains the SurfaceControl object, which we need to look up the
             // layerId. Since we don't know which callback contains the SurfaceControl, iterate
@@ -1220,7 +1227,7 @@
         flags |= ISurfaceComposer::eEarlyWakeupEnd;
     }
 
-    sp<IBinder> applyToken = mApplyToken ? mApplyToken : sApplyToken;
+    sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
 
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
     sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
@@ -1242,11 +1249,15 @@
 
 sp<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder();
 
+std::mutex SurfaceComposerClient::Transaction::sApplyTokenMutex;
+
 sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() {
+    std::scoped_lock lock{sApplyTokenMutex};
     return sApplyToken;
 }
 
 void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
+    std::scoped_lock lock{sApplyTokenMutex};
     sApplyToken = applyToken;
 }
 
@@ -2086,6 +2097,32 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRateCategory(
+        const sp<SurfaceControl>& sc, int8_t category, bool smoothSwitchOnly) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eFrameRateCategoryChanged;
+    s->frameRateCategory = category;
+    s->frameRateCategorySmoothSwitchOnly = smoothSwitchOnly;
+    return *this;
+}
+
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc,
+                                                                  int8_t strategy) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eFrameRateSelectionStrategyChanged;
+    s->frameRateSelectionStrategy = strategy;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint(
         const sp<SurfaceControl>& sc, int32_t fixedTransformHint) {
     layer_state_t* s = getLayerState(sc);
@@ -2568,6 +2605,7 @@
         outMode.xDpi = mode.xDpi;
         outMode.yDpi = mode.yDpi;
         outMode.refreshRate = mode.refreshRate;
+        outMode.vsyncRate = mode.vsyncRate;
         outMode.appVsyncOffset = mode.appVsyncOffset;
         outMode.sfVsyncOffset = mode.sfVsyncOffset;
         outMode.presentationDeadline = mode.presentationDeadline;
@@ -2750,16 +2788,16 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& uids,
+status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& appIds,
                                                          std::vector<float>& thresholds) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(uids, thresholds);
+            ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(appIds, thresholds);
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
+status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(uid,
+            ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(appId,
                                                                                       threshold);
     return statusTFromBinderStatus(status);
 }
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index a4d9e77..a590c86 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/native/libs/nativewindow"
+    },
+    {
+      "path": "frameworks/native/services/surfaceflinger"
     }
   ],
   "presubmit": [
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 52af9d5..2eb6bd6 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -43,7 +43,7 @@
 }
 
 bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
-    return x >= frameLeft && x < frameRight && y >= frameTop && y < frameBottom;
+    return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
 }
 
 bool WindowInfo::supportsSplitTouch() const {
@@ -59,18 +59,15 @@
 }
 
 bool WindowInfo::overlaps(const WindowInfo* other) const {
-    const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
-    return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
-            frameTop < other->frameBottom && frameBottom > other->frameTop;
+    return !frame.isEmpty() && frame.left < other->frame.right && frame.right > other->frame.left &&
+            frame.top < other->frame.bottom && frame.bottom > other->frame.top;
 }
 
 bool WindowInfo::operator==(const WindowInfo& info) const {
     return info.token == token && info.id == id && info.name == name &&
-            info.dispatchingTimeout == dispatchingTimeout && info.frameLeft == frameLeft &&
-            info.frameTop == frameTop && info.frameRight == frameRight &&
-            info.frameBottom == frameBottom && info.surfaceInset == surfaceInset &&
-            info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
-            info.touchableRegion.hasSameRects(touchableRegion) &&
+            info.dispatchingTimeout == dispatchingTimeout && info.frame == frame &&
+            info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
+            info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
             info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
             info.ownerUid == ownerUid && info.packageName == packageName &&
             info.inputConfig == inputConfig && info.displayId == displayId &&
@@ -103,10 +100,7 @@
         parcel->writeInt32(layoutParamsFlags.get()) ?:
         parcel->writeInt32(
                 static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
-        parcel->writeInt32(frameLeft) ?:
-        parcel->writeInt32(frameTop) ?:
-        parcel->writeInt32(frameRight) ?:
-        parcel->writeInt32(frameBottom) ?:
+        parcel->write(frame) ?:
         parcel->writeInt32(surfaceInset) ?:
         parcel->writeFloat(globalScaleFactor) ?:
         parcel->writeFloat(alpha) ?:
@@ -155,10 +149,7 @@
     // clang-format off
     status = parcel->readInt32(&lpFlags) ?:
         parcel->readInt32(&lpType) ?:
-        parcel->readInt32(&frameLeft) ?:
-        parcel->readInt32(&frameTop) ?:
-        parcel->readInt32(&frameRight) ?:
-        parcel->readInt32(&frameBottom) ?:
+        parcel->read(frame) ?:
         parcel->readInt32(&surfaceInset) ?:
         parcel->readFloat(&globalScaleFactor) ?:
         parcel->readFloat(&alpha) ?:
diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl
index ce30426..b057653 100644
--- a/libs/gui/aidl/android/gui/DisplayMode.aidl
+++ b/libs/gui/aidl/android/gui/DisplayMode.aidl
@@ -29,7 +29,9 @@
     float yDpi = 0.0f;
     int[] supportedHdrTypes;
 
+    // Some modes have peak refresh rate lower than the panel vsync rate.
     float refreshRate = 0.0f;
+    float vsyncRate = 0.0f;
     long appVsyncOffset = 0;
     long sfVsyncOffset = 0;
     long presentationDeadline = 0;
diff --git a/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
index 9781ca9..008ef19 100644
--- a/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
+++ b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
@@ -18,6 +18,7 @@
 
 import android.gui.BitTube;
 import android.gui.ParcelableVsyncEventData;
+import android.gui.SchedulingPolicy;
 
 /** @hide */
 interface IDisplayEventConnection {
@@ -44,4 +45,9 @@
      * getLatestVsyncEventData() gets the latest vsync event data.
      */
     ParcelableVsyncEventData getLatestVsyncEventData();
+
+    /*
+     * getSchedulingPolicy() used in tests to validate the binder thread pririty
+     */
+    SchedulingPolicy getSchedulingPolicy();
 }
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 516d159..a7cf5dd 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -46,6 +46,7 @@
 import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
 import android.gui.ARect;
+import android.gui.SchedulingPolicy;
 import android.gui.StalledTransactionInfo;
 import android.gui.StaticDisplayInfo;
 import android.gui.WindowInfosListenerInfo;
@@ -231,20 +232,20 @@
      * The subregion can be optionally rotated.  It will also be scaled to
      * match the size of the output buffer.
      */
-    void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
+    oneway void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
 
     /**
      * Capture the specified screen. This requires the READ_FRAME_BUFFER
      * permission.
      */
-    void captureDisplayById(long displayId, IScreenCaptureListener listener);
+    oneway void captureDisplayById(long displayId, IScreenCaptureListener listener);
 
     /**
      * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
      * This requires READ_FRAME_BUFFER permission. This function will fail if there
      * is a secure window on screen
      */
-    void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
+    oneway void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
 
     /**
      * Clears the frame statistics for animations.
@@ -281,8 +282,6 @@
      */
     List<LayerDebugInfo> getLayerDebugInfo();
 
-    boolean getColorManagement();
-
     /**
      * Gets the composition preference of the default data space and default pixel format,
      * as well as the wide color gamut data space and wide color gamut pixel format.
@@ -481,14 +480,38 @@
      */
     void setOverrideFrameRate(int uid, float frameRate);
 
-    oneway void updateSmallAreaDetection(in int[] uids, in float[] thresholds);
+    oneway void updateSmallAreaDetection(in int[] appIds, in float[] thresholds);
 
     /**
-     * Set the small area detection threshold for a specified uid by SmallAreaDetectionController.
-     * Passing the threshold and uid to SurfaceFlinger to update the uid-threshold mapping
+     * Set the small area detection threshold for a specified appId by SmallAreaDetectionController.
+     * Passing the threshold and appId to SurfaceFlinger to update the appId-threshold mapping
      * in the scheduler.
      */
-    oneway void setSmallAreaDetectionThreshold(int uid, float threshold);
+    oneway void setSmallAreaDetectionThreshold(int appId, float threshold);
+
+    /**
+     * Enables or disables the frame rate overlay in the top left corner.
+     * Requires root or android.permission.HARDWARE_TEST
+     */
+    void enableRefreshRateOverlay(boolean active);
+
+    /**
+     * Enables or disables the debug flash.
+     * Requires root or android.permission.HARDWARE_TEST
+     */
+    void setDebugFlash(int delay);
+
+    /**
+     * Force composite ahead of next VSYNC.
+     * Requires root or android.permission.HARDWARE_TEST
+     */
+    void scheduleComposite();
+
+    /**
+     * Force commit ahead of next VSYNC.
+     * Requires root or android.permission.HARDWARE_TEST
+     */
+    void scheduleCommit();
 
     /**
      * Gets priority of the RenderEngine in SurfaceFlinger.
@@ -523,4 +546,6 @@
      * applied in SurfaceFlinger due to an unsignaled fence. Otherwise, null is returned.
      */
     @nullable StalledTransactionInfo getStalledTransactionInfo(int pid);
+
+    SchedulingPolicy getSchedulingPolicy();
 }
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
index 68781ce..920257c 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
@@ -19,6 +19,7 @@
 import android.gui.CreateSurfaceResult;
 import android.gui.FrameStats;
 import android.gui.LayerMetadata;
+import android.gui.SchedulingPolicy;
 
 /** @hide */
 interface ISurfaceComposerClient {
@@ -60,4 +61,6 @@
     CreateSurfaceResult mirrorSurface(IBinder mirrorFromHandle);
 
     CreateSurfaceResult mirrorDisplay(long displayId);
+
+    SchedulingPolicy getSchedulingPolicy();
 }
diff --git a/services/inputflinger/host/main.cpp b/libs/gui/aidl/android/gui/SchedulingPolicy.aidl
similarity index 64%
copy from services/inputflinger/host/main.cpp
copy to libs/gui/aidl/android/gui/SchedulingPolicy.aidl
index 0a517cc..4f7cf0a 100644
--- a/services/inputflinger/host/main.cpp
+++ b/libs/gui/aidl/android/gui/SchedulingPolicy.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-#include <binder/BinderService.h>
-#include "InputFlinger.h"
+package android.gui;
 
-using namespace android;
-
-int main(int, char**) {
-    ProcessState::self()->setThreadPoolMaxThreadCount(4);
-    BinderService<InputFlinger>::publishAndJoinThreadPool(true);
-    return 0;
+/** @hide */
+parcelable SchedulingPolicy {
+    int policy;
+    int priority;
 }
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
index 82e1b5a..cd738ac 100644
--- a/libs/gui/fuzzer/Android.bp
+++ b/libs/gui/fuzzer/Android.bp
@@ -24,6 +24,7 @@
 
 cc_defaults {
     name: "libgui_fuzzer_defaults",
+    defaults: ["android.hardware.power-ndk_shared"],
     static_libs: [
         "android.hidl.token@1.0-utils",
         "libbinder_random_parcel",
@@ -46,7 +47,6 @@
         "android.hardware.configstore-utils",
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
-        "android.hardware.power-V4-cpp",
         "android.hidl.token@1.0",
         "libSurfaceFlingerProp",
         "libgui",
@@ -72,6 +72,14 @@
             "android-media-fuzzing-reports@google.com",
         ],
         componentid: 155276,
+        hotlists: [
+            "4593311",
+        ],
+        description: "The fuzzer targets the APIs of libgui library",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
     },
 }
 
@@ -82,6 +90,7 @@
     ],
     defaults: [
         "libgui_fuzzer_defaults",
+        "service_fuzzer_defaults",
     ],
 }
 
@@ -92,6 +101,7 @@
     ],
     defaults: [
         "libgui_fuzzer_defaults",
+        "service_fuzzer_defaults",
     ],
 }
 
diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
index 6e4f074..0d2a52b 100644
--- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
@@ -62,7 +62,10 @@
 
         }
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: {
-            event.hotplug = DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/};
+            event.hotplug =
+                    DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/,
+                                                         fdp->ConsumeIntegral<
+                                                                 int32_t>() /*connectionError*/};
             break;
         }
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 67915f9..3142103 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -110,7 +110,6 @@
                 (override));
     MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override));
     MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector<gui::LayerDebugInfo>*), (override));
-    MOCK_METHOD(binder::Status, getColorManagement, (bool*), (override));
     MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*),
                 (override));
     MOCK_METHOD(binder::Status, getDisplayedContentSamplingAttributes,
@@ -151,6 +150,10 @@
     MOCK_METHOD(binder::Status, getDisplayDecorationSupport,
                 (const sp<IBinder>&, std::optional<gui::DisplayDecorationSupport>*), (override));
     MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override));
+    MOCK_METHOD(binder::Status, enableRefreshRateOverlay, (bool), (override));
+    MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
+    MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
+    MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
     MOCK_METHOD(binder::Status, updateSmallAreaDetection,
                 (const std::vector<int32_t>&, const std::vector<float>&), (override));
     MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (int32_t, float), (override));
@@ -163,6 +166,7 @@
     MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override));
     MOCK_METHOD(binder::Status, getStalledTransactionInfo,
                 (int32_t, std::optional<gui::StalledTransactionInfo>*), (override));
+    MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
 };
 
 class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient {
@@ -183,6 +187,8 @@
 
     MOCK_METHOD(binder::Status, mirrorDisplay,
                 (int64_t displayId, gui::CreateSurfaceResult* outResult), (override));
+
+    MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
 };
 
 class FakeDisplayEventDispatcher : public DisplayEventDispatcher {
@@ -194,6 +200,7 @@
 
     MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData));
     MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool));
+    MOCK_METHOD2(dispatchHotplugConnectionError, void(nsecs_t, int32_t));
     MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t));
     MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId));
     MOCK_METHOD3(dispatchFrameRateOverrides,
diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
index 95b7f39..4daa3be 100644
--- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Boost.h>
 #include <fuzzbinder/libbinder_driver.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
@@ -39,10 +39,13 @@
                                          ui::ColorMode::BT2100_HLG,
                                          ui::ColorMode::DISPLAY_BT2020};
 
-constexpr hardware::power::Boost kBoost[] = {
-        hardware::power::Boost::INTERACTION,   hardware::power::Boost::DISPLAY_UPDATE_IMMINENT,
-        hardware::power::Boost::ML_ACC,        hardware::power::Boost::AUDIO_LAUNCH,
-        hardware::power::Boost::CAMERA_LAUNCH, hardware::power::Boost::CAMERA_SHOT,
+constexpr aidl::android::hardware::power::Boost kBoost[] = {
+        aidl::android::hardware::power::Boost::INTERACTION,
+        aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT,
+        aidl::android::hardware::power::Boost::ML_ACC,
+        aidl::android::hardware::power::Boost::AUDIO_LAUNCH,
+        aidl::android::hardware::power::Boost::CAMERA_LAUNCH,
+        aidl::android::hardware::power::Boost::CAMERA_SHOT,
 };
 
 constexpr gui::TouchOcclusionMode kMode[] = {
@@ -175,10 +178,8 @@
     windowInfo->name = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
     windowInfo->layoutParamsFlags = mFdp.PickValueInArray(kFlags);
     windowInfo->layoutParamsType = mFdp.PickValueInArray(kType);
-    windowInfo->frameLeft = mFdp.ConsumeIntegral<int32_t>();
-    windowInfo->frameTop = mFdp.ConsumeIntegral<int32_t>();
-    windowInfo->frameRight = mFdp.ConsumeIntegral<int32_t>();
-    windowInfo->frameBottom = mFdp.ConsumeIntegral<int32_t>();
+    windowInfo->frame = Rect(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>(),
+                             mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>());
     windowInfo->surfaceInset = mFdp.ConsumeIntegral<int32_t>();
     windowInfo->alpha = mFdp.ConsumeFloatingPointInRange<float>(0, 1);
     ui::Transform transform(mFdp.PickValueInArray(kOrientation));
@@ -284,7 +285,7 @@
     SurfaceComposerClient::doUncacheBufferTransaction(mFdp.ConsumeIntegral<uint64_t>());
 
     SurfaceComposerClient::setDisplayBrightness(displayToken, getBrightness(&mFdp));
-    hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost);
+    aidl::android::hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost);
     SurfaceComposerClient::notifyPowerBoost((int32_t)boostId);
 
     String8 surfaceName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index a49a859..02d7c4d 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,9 +17,10 @@
 #ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 #define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/BufferItemConsumer.h>
 #include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/Flags.h>
+#include <gui/IGraphicBufferProducer.h>
 #include <gui/SurfaceComposerClient.h>
 
 #include <utils/Condition.h>
@@ -58,6 +59,10 @@
 
 protected:
     void onSidebandStreamChanged() override EXCLUDES(mMutex);
+#if FLAG_BQ_SET_FRAME_RATE
+    void onSetFrameRate(float frameRate, int8_t compatibility,
+                        int8_t changeFrameRateStrategy) override;
+#endif
 
 private:
     const wp<BLASTBufferQueue> mBLASTBufferQueue;
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 690587f..2756277 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -19,9 +19,10 @@
 
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
+#include <gui/Flags.h>
+#include <gui/IConsumerListener.h>
 #include <gui/IGraphicBufferConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
-#include <gui/IConsumerListener.h>
 
 namespace android {
 
@@ -69,6 +70,10 @@
         void addAndGetFrameTimestamps(
                 const NewFrameEventsEntry* newTimestamps,
                 FrameEventHistoryDelta* outDelta) override;
+#if FLAG_BQ_SET_FRAME_RATE
+        void onSetFrameRate(float frameRate, int8_t compatibility,
+                            int8_t changeFrameRateStrategy) 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/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 1d13dab..38805d0 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -18,6 +18,7 @@
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
 #include <gui/BufferQueueDefs.h>
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
 
 namespace android {
@@ -201,6 +202,11 @@
 
     // See IGraphicBufferProducer::setAutoPrerotation
     virtual status_t setAutoPrerotation(bool autoPrerotation);
+#if FLAG_BQ_SET_FRAME_RATE
+    // See IGraphicBufferProducer::setFrameRate
+    status_t setFrameRate(float frameRate, int8_t compatibility,
+                          int8_t changeFrameRateStrategy) override;
+#endif
 
 protected:
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 1df9b11..9fef512 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -110,6 +110,7 @@
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                        VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
+    void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                              nsecs_t vsyncPeriod) override;
     void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
@@ -137,4 +138,4 @@
     static constexpr size_t kMaxStartTimes = 250;
 };
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 140efa6..fe2dd20 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -53,6 +53,9 @@
                                VsyncEventData vsyncEventData) = 0;
     virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
                                  bool connected) = 0;
+
+    virtual void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) = 0;
+
     virtual void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                                      nsecs_t vsyncPeriod) = 0;
     // AChoreographer-specific hook for processing null-events so that looper
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 7fd6c35..79582ce 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -88,6 +88,7 @@
 
         struct Hotplug {
             bool connected;
+            int32_t connectionError __attribute__((aligned(4)));
         };
 
         struct ModeChange {
diff --git a/services/inputflinger/host/main.cpp b/libs/gui/include/gui/Flags.h
similarity index 64%
copy from services/inputflinger/host/main.cpp
copy to libs/gui/include/gui/Flags.h
index 0a517cc..a2cff56 100644
--- a/services/inputflinger/host/main.cpp
+++ b/libs/gui/include/gui/Flags.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-#include <binder/BinderService.h>
-#include "InputFlinger.h"
+#pragma once
 
-using namespace android;
-
-int main(int, char**) {
-    ProcessState::self()->setThreadPoolMaxThreadCount(4);
-    BinderService<InputFlinger>::publishAndJoinThreadPool(true);
-    return 0;
-}
+// TODO(281695725): replace this with build time flags, whenever they are available
+#ifndef FLAG_BQ_SET_FRAME_RATE
+#define FLAG_BQ_SET_FRAME_RATE false
+#endif
\ No newline at end of file
diff --git a/libs/renderengine/include/renderengine/Image.h b/libs/gui/include/gui/FrameRateUtils.h
similarity index 68%
rename from libs/renderengine/include/renderengine/Image.h
rename to libs/gui/include/gui/FrameRateUtils.h
index 3bb4731..16896ef 100644
--- a/libs/renderengine/include/renderengine/Image.h
+++ b/libs/gui/include/gui/FrameRateUtils.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,16 +16,11 @@
 
 #pragma once
 
-struct ANativeWindowBuffer;
+#include <stdint.h>
 
 namespace android {
-namespace renderengine {
 
-class Image {
-public:
-    virtual ~Image() = default;
-    virtual bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) = 0;
-};
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+                       const char* inFunctionName, bool privileged = false);
 
-} // namespace renderengine
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 0ab2399..e183bf2 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -19,6 +19,8 @@
 #include <binder/IInterface.h>
 #include <binder/SafeInterface.h>
 
+#include <gui/Flags.h>
+
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
@@ -90,6 +92,12 @@
     // WARNING: This method can only be called when the BufferQueue is in the consumer's process.
     virtual void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/,
                                           FrameEventHistoryDelta* /*outDelta*/) {}
+
+#if FLAG_BQ_SET_FRAME_RATE
+    // Notifies the consumer of a setFrameRate call from the producer side.
+    virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+                                int8_t /*changeFrameRateStrategy*/) {}
+#endif
 };
 
 #ifndef NO_BINDER
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 98df834..3562906 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,6 +31,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <gui/Flags.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -676,6 +677,12 @@
     // the width and height used for dequeueBuffer will be additionally swapped.
     virtual status_t setAutoPrerotation(bool autoPrerotation);
 
+#if FLAG_BQ_SET_FRAME_RATE
+    // Sets the apps intended frame rate.
+    virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+                                  int8_t changeFrameRateStrategy);
+#endif
+
     struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
         RequestBufferOutput() = default;
 
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 3ff6735..a836f46 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -199,6 +199,7 @@
         SET_BOOT_DISPLAY_MODE,         // Deprecated. Autogenerated by .aidl now.
         CLEAR_BOOT_DISPLAY_MODE,       // Deprecated. Autogenerated by .aidl now.
         SET_OVERRIDE_FRAME_RATE,       // Deprecated. Autogenerated by .aidl now.
+        GET_SCHEDULING_POLICY,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 39bcb4a..364a57b 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -120,14 +120,17 @@
     status_t readFromParcel(const Parcel* input) override;
 
     JankData();
-    JankData(int64_t frameVsyncId, int32_t jankType)
-          : frameVsyncId(frameVsyncId), jankType(jankType) {}
+    JankData(int64_t frameVsyncId, int32_t jankType, nsecs_t frameIntervalNs)
+          : frameVsyncId(frameVsyncId), jankType(jankType), frameIntervalNs(frameIntervalNs) {}
 
     // Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
     int64_t frameVsyncId;
 
     // Bitmask of janks that occurred
     int32_t jankType;
+
+    // Expected duration of the frame
+    nsecs_t frameIntervalNs;
 };
 
 class SurfaceStats : public Parcelable {
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
index 1dddeba..bf354e7 100644
--- a/libs/gui/include/gui/JankInfo.h
+++ b/libs/gui/include/gui/JankInfo.h
@@ -46,6 +46,8 @@
     // where the previous frame was presented in the current frame's expected vsync. This pushes the
     // current frame to the next vsync. The behavior is similar to BufferStuffing.
     SurfaceFlingerStuffing = 0x100,
+    // Frame was dropped, as a newer frame was ready and replaced this frame.
+    Dropped = 0x200,
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 62e5f89..e1dc791 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -181,7 +181,7 @@
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
         eColorChanged = 0x00010000,
-        /* unused = 0x00020000, */
+        eFrameRateCategoryChanged = 0x00020000,
         eBufferTransformChanged = 0x00040000,
         eTransformToDisplayInverseChanged = 0x00080000,
         eCropChanged = 0x00100000,
@@ -197,7 +197,7 @@
         eInputInfoChanged = 0x40000000,
         eCornerRadiusChanged = 0x80000000,
         eDestinationFrameChanged = 0x1'00000000,
-        /* unused = 0x2'00000000, */
+        eFrameRateSelectionStrategyChanged = 0x2'00000000,
         eBackgroundColorChanged = 0x4'00000000,
         eMetadataChanged = 0x8'00000000,
         eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -213,7 +213,6 @@
         eTrustedOverlayChanged = 0x4000'00000000,
         eDropInputModeChanged = 0x8000'00000000,
         eExtendedRangeBrightnessChanged = 0x10000'00000000,
-
     };
 
     layer_state_t();
@@ -265,9 +264,12 @@
     // Changes affecting child states.
     static constexpr uint64_t AFFECTS_CHILDREN = layer_state_t::GEOMETRY_CHANGES |
             layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged |
+            layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
             layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
             layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
-            layer_state_t::eFrameRateChanged | layer_state_t::eFixedTransformHintChanged;
+            layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+            layer_state_t::eFrameRateSelectionStrategyChanged |
+            layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
 
     // Changes affecting data sent to input.
     static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged |
@@ -275,8 +277,8 @@
             layer_state_t::eLayerStackChanged;
 
     // Changes that affect the visible region on a display.
-    static constexpr uint64_t VISIBLE_REGION_CHANGES =
-            layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES;
+    static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
+            layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged;
 
     bool hasValidBuffer() const;
     void sanitize(int32_t permissions);
@@ -357,6 +359,13 @@
     // Default frame rate compatibility used to set the layer refresh rate votetype.
     int8_t defaultFrameRateCompatibility;
 
+    // Frame rate category to suggest what frame rate range a surface should run.
+    int8_t frameRateCategory;
+    bool frameRateCategorySmoothSwitchOnly;
+
+    // Strategy of the layer for frame rate selection.
+    int8_t frameRateSelectionStrategy;
+
     // Set by window manager indicating the layer and all its children are
     // in a different orientation than the display. The hint suggests that
     // the graphic producers should receive a transform hint as if the
@@ -472,16 +481,6 @@
     return compare_type(lhs.token, rhs.token);
 }
 
-// Returns true if the frameRate is valid.
-//
-// @param frameRate the frame rate in Hz
-// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
-// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
-// @param functionName calling function or nullptr. Used for logging
-// @param privileged whether caller has unscoped surfaceflinger access
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
-                       const char* functionName, bool privileged = false);
-
 }; // namespace android
 
 #endif // ANDROID_SF_LAYER_STATE_H
diff --git a/libs/gui/include/gui/SchedulingPolicy.h b/libs/gui/include/gui/SchedulingPolicy.h
new file mode 100644
index 0000000..48c9f0c
--- /dev/null
+++ b/libs/gui/include/gui/SchedulingPolicy.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sched.h>
+
+#include <android/gui/SchedulingPolicy.h>
+#include <binder/Status.h>
+
+namespace android::gui {
+
+static binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    outPolicy->policy = sched_getscheduler(0);
+    if (outPolicy->policy < 0) {
+        return binder::Status::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    struct sched_param param;
+    if (sched_getparam(0, &param) < 0) {
+        return binder::Status::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    outPolicy->priority = param.sched_priority;
+    return binder::Status::ok();
+}
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 7c55100..54c3aa7 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -203,15 +203,15 @@
     // by GameManager.
     static status_t setOverrideFrameRate(uid_t uid, float frameRate);
 
-    // Update the small area detection whole uid-threshold mappings by same size uid and threshold
-    // vector.
+    // Update the small area detection whole appId-threshold mappings by same size appId and
+    // threshold vector.
     // Ref:setSmallAreaDetectionThreshold.
-    static status_t updateSmallAreaDetection(std::vector<int32_t>& uids,
+    static status_t updateSmallAreaDetection(std::vector<int32_t>& appIds,
                                              std::vector<float>& thresholds);
 
-    // Sets the small area detection threshold to particular apps (uid). Passing value 0 means
+    // Sets the small area detection threshold to particular apps (appId). Passing value 0 means
     // to disable small area detection to the app.
-    static status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+    static status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
 
     // Switches on/off Auto Low Latency Mode on the connected display. This should only be
     // called if the connected display supports Auto Low Latency Mode as reported by
@@ -422,6 +422,7 @@
     class Transaction : public Parcelable {
     private:
         static sp<IBinder> sApplyToken;
+        static std::mutex sApplyTokenMutex;
         void releaseBufferIfOverwriting(const layer_state_t& state);
         static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
 
@@ -685,6 +686,11 @@
         Transaction& setDefaultFrameRateCompatibility(const sp<SurfaceControl>& sc,
                                                       int8_t compatibility);
 
+        Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category,
+                                          bool smoothSwitchOnly);
+
+        Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
+
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
         // the graphic producers should receive a transform hint as if the
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 7ff7387..bd2eb74 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -194,10 +194,7 @@
     std::chrono::nanoseconds dispatchingTimeout = std::chrono::seconds(5);
 
     /* These values are filled in by SurfaceFlinger. */
-    int32_t frameLeft = -1;
-    int32_t frameTop = -1;
-    int32_t frameRight = -1;
-    int32_t frameBottom = -1;
+    Rect frame = Rect::INVALID_RECT;
 
     /*
      * SurfaceFlinger consumes this value to shrink the computed frame. This is
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
new file mode 100644
index 0000000..a16be78
--- /dev/null
+++ b/libs/gui/libgui_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.graphics.libgui.flags"
+
+flag {
+  name: "bq_setframerate"
+  namespace: "core_graphics"
+  description: "This flag controls plumbing setFrameRate thru BufferQueue"
+  bug: "281695725"
+  is_fixed_read_only: true
+}
+
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 462ce6e..38c0eed 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -15,9 +15,13 @@
     name: "libgui_test",
     test_suites: ["device-tests"],
 
-    cflags: [
+    defaults: ["libgui-defaults"],
+
+    cppflags: [
         "-Wall",
         "-Werror",
+        "-Wno-extra",
+        "-DFLAG_BQ_SET_FRAME_RATE=true",
     ],
 
     srcs: [
@@ -28,6 +32,7 @@
         "CompositorTiming_test.cpp",
         "CpuConsumer_test.cpp",
         "EndToEndNativeInputTest.cpp",
+        "FrameRateUtilsTest.cpp",
         "DisplayInfo_test.cpp",
         "DisplayedContentSampling_test.cpp",
         "FillBuffer.cpp",
@@ -53,19 +58,12 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "libSurfaceFlingerProp",
-        "libbase",
-        "liblog",
-        "libEGL",
         "libGLESv1_CM",
-        "libGLESv2",
-        "libbinder",
-        "libcutils",
-        "libgui",
-        "libhidlbase",
         "libinput",
-        "libui",
-        "libutils",
-        "libnativewindow",
+    ],
+
+    static_libs: [
+        "libgmock",
     ],
 
     header_libs: ["libsurfaceflinger_headers"],
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index cd90168..9893c71 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -31,10 +31,13 @@
 #include <gui/test/CallbackUtils.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
+#include <tests/utils/ScreenshotUtils.h>
 #include <ui/DisplayMode.h>
 #include <ui/DisplayState.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+#include <ui/Size.h>
 #include <ui/Transform.h>
 
 #include <gtest/gtest.h>
@@ -197,18 +200,23 @@
         ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
               displayState.orientation);
 
+        mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth,
+                                                     mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
+                                                     ISurfaceComposerClient::eFXSurfaceBufferState,
+                                                     /*parent*/ nullptr);
+
+        t.setLayerStack(mRootSurfaceControl, ui::DEFAULT_LAYER_STACK)
+                .setLayer(mRootSurfaceControl, std::numeric_limits<int32_t>::max())
+                .show(mRootSurfaceControl)
+                .apply();
+
         mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth,
                                                  mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
                                                  ISurfaceComposerClient::eFXSurfaceBufferState,
-                                                 /*parent*/ nullptr);
-        t.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
-                .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
-                .show(mSurfaceControl)
-                .setDataspace(mSurfaceControl, ui::Dataspace::V0_SRGB)
-                .apply();
+                                                 /*parent*/ mRootSurfaceControl->getHandle());
 
-        mCaptureArgs.displayToken = mDisplayToken;
-        mCaptureArgs.dataspace = ui::Dataspace::V0_SRGB;
+        mCaptureArgs.sourceCrop = Rect(ui::Size(mDisplayWidth, mDisplayHeight));
+        mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
     }
 
     void setUpProducer(BLASTBufferQueueHelper& adapter, sp<IGraphicBufferProducer>& producer,
@@ -295,21 +303,6 @@
         captureBuf->unlock();
     }
 
-    static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
-                                   ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerServiceAIDL::getComposerService();
-        SurfaceComposerClient::Transaction().apply(true);
-
-        const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        binder::Status status = sf->captureDisplay(captureArgs, captureListener);
-        status_t err = gui::aidl_utils::statusTFromBinderStatus(status);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        captureResults = captureListener->waitForResults();
-        return fenceStatus(captureResults.fenceResult);
-    }
-
     void queueBuffer(sp<IGraphicBufferProducer> igbp, uint8_t r, uint8_t g, uint8_t b,
                      nsecs_t presentTimeDelay) {
         int slot;
@@ -342,11 +335,12 @@
     sp<IBinder> mDisplayToken;
 
     sp<SurfaceControl> mSurfaceControl;
+    sp<SurfaceControl> mRootSurfaceControl;
 
     uint32_t mDisplayWidth;
     uint32_t mDisplayHeight;
 
-    DisplayCaptureArgs mCaptureArgs;
+    LayerCaptureArgs mCaptureArgs;
     ScreenCaptureResults mCaptureResults;
     sp<CountProducerListener> mProducerListener;
 };
@@ -364,7 +358,9 @@
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
     sp<SurfaceControl> updateSurface =
             mClient->createSurface(String8("UpdateTest"), mDisplayWidth / 2, mDisplayHeight / 2,
-                                   PIXEL_FORMAT_RGBA_8888);
+                                   PIXEL_FORMAT_RGBA_8888,
+                                   ISurfaceComposerClient::eFXSurfaceBufferState,
+                                   /*parent*/ mRootSurfaceControl->getHandle());
     adapter.update(updateSurface, mDisplayWidth / 2, mDisplayHeight / 2);
     ASSERT_EQ(updateSurface, adapter.getSurfaceControl());
     sp<IGraphicBufferProducer> igbProducer;
@@ -447,10 +443,11 @@
     igbProducer->queueBuffer(slot, input, &qbOutput);
     ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
 
-    adapter.waitForCallbacks();
+    // ensure the buffer queue transaction has been committed
+    Transaction().apply(true /* synchronous */);
 
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -531,9 +528,11 @@
     igbProducer->queueBuffer(slot, input, &qbOutput);
     ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
 
-    adapter.waitForCallbacks();
+    // ensure the buffer queue transaction has been committed
+    Transaction().apply(true /* synchronous */);
+
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
 
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b,
@@ -549,16 +548,6 @@
             (mDisplayWidth < mDisplayHeight) ? mDisplayWidth / 2 : mDisplayHeight / 2;
     int32_t finalCropSideLength = bufferSideLength / 2;
 
-    auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
-                                     ISurfaceComposerClient::eFXSurfaceEffect);
-    ASSERT_NE(nullptr, bg.get());
-    Transaction t;
-    t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
-            .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
-            .setColor(bg, half3{0, 0, 0})
-            .setLayer(bg, 0)
-            .apply();
-
     BLASTBufferQueueHelper adapter(mSurfaceControl, bufferSideLength, bufferSideLength);
     sp<IGraphicBufferProducer> igbProducer;
     setUpProducer(adapter, igbProducer);
@@ -590,9 +579,11 @@
     igbProducer->queueBuffer(slot, input, &qbOutput);
     ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
 
-    adapter.waitForCallbacks();
+    // ensure the buffer queue transaction has been committed
+    Transaction().apply(true /* synchronous */);
+
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(checkScreenCapture(r, g, b,
                                                {10, 10, (int32_t)bufferSideLength - 10,
                                                 (int32_t)bufferSideLength - 10}));
@@ -603,17 +594,6 @@
 }
 
 TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
-    // add black background
-    auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
-                                     ISurfaceComposerClient::eFXSurfaceEffect);
-    ASSERT_NE(nullptr, bg.get());
-    Transaction t;
-    t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
-            .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
-            .setColor(bg, half3{0, 0, 0})
-            .setLayer(bg, 0)
-            .apply();
-
     Rect windowSize(1000, 1000);
     Rect bufferSize(windowSize);
     Rect bufferCrop(200, 200, 700, 700);
@@ -653,9 +633,10 @@
     igbProducer->queueBuffer(slot, input, &qbOutput);
     ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
 
-    adapter.waitForCallbacks();
+    // ensure the buffer queue transaction has been committed
+    Transaction().apply(true /* synchronous */);
 
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
 
     // Verify cropped region is scaled correctly.
     ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0, {10, 10, 490, 490}));
@@ -670,17 +651,6 @@
 }
 
 TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToWindowSize) {
-    // add black background
-    auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
-                                     ISurfaceComposerClient::eFXSurfaceEffect);
-    ASSERT_NE(nullptr, bg.get());
-    Transaction t;
-    t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
-            .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
-            .setColor(bg, half3{0, 0, 0})
-            .setLayer(bg, 0)
-            .apply();
-
     Rect windowSize(1000, 1000);
     Rect bufferSize(500, 500);
     Rect bufferCrop(100, 100, 350, 350);
@@ -720,9 +690,10 @@
     igbProducer->queueBuffer(slot, input, &qbOutput);
     ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
 
-    adapter.waitForCallbacks();
+    // ensure the buffer queue transaction has been committed
+    Transaction().apply(true /* synchronous */);
 
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     // Verify cropped region is scaled correctly.
     ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0, {10, 10, 490, 490}));
     ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 255, 0, {10, 510, 490, 990}));
@@ -767,10 +738,12 @@
                                                        NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
                                                        Fence::NO_FENCE);
         igbProducer->queueBuffer(slot, input, &qbOutput);
-        adapter.waitForCallbacks();
+
+        // ensure the buffer queue transaction has been committed
+        Transaction().apply(true /* synchronous */);
     }
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
 
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b,
@@ -802,10 +775,11 @@
                                                        NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
                                                        0, Fence::NO_FENCE);
         igbProducer->queueBuffer(slot, input, &qbOutput);
-        adapter.waitForCallbacks();
+        // ensure the buffer queue transaction has been committed
+        Transaction().apply(true /* synchronous */);
     }
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     // verify we still scale the buffer to the new size (half the screen height)
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b,
@@ -840,12 +814,12 @@
     transactionCallback.getCallbackData(&callbackData);
 
     // capture screen and verify that it is green
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(0, 255, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 
     mProducerListener->waitOnNumberReleased(1);
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -885,7 +859,7 @@
     transactionCallback.getCallbackData(&callbackData);
 
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -933,7 +907,7 @@
     transactionCallback.getCallbackData(&callbackData);
 
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -983,7 +957,7 @@
     transactionCallback.getCallbackData(&callbackData);
 
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -1041,7 +1015,7 @@
     transactionCallback.getCallbackData(&callbackData);
 
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -1074,13 +1048,13 @@
     transactionCallback.getCallbackData(&callbackData);
 
     // capture screen and verify that it is blue
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(0, 0, 255, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 
     mProducerListener->waitOnNumberReleased(2);
     // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(255, 0, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
@@ -1176,7 +1150,7 @@
 
     CallbackData callbackData;
     transactionCallback.getCallbackData(&callbackData);
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
     sync.apply();
@@ -1195,7 +1169,7 @@
                 mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
                                        PIXEL_FORMAT_RGBA_8888,
                                        ISurfaceComposerClient::eFXSurfaceBufferState,
-                                       /*parent*/ nullptr);
+                                       /*parent*/ mRootSurfaceControl->getHandle());
         Transaction()
                 .setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
                 .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
@@ -1220,7 +1194,7 @@
         CallbackData callbackData;
         transactionCallback.getCallbackData(&callbackData);
         // capture screen and verify that it is red
-        ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
         ASSERT_NO_FATAL_FAILURE(
                 checkScreenCapture(255, 0, 0,
                                    {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -1238,7 +1212,7 @@
                 mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
                                        PIXEL_FORMAT_RGBA_8888,
                                        ISurfaceComposerClient::eFXSurfaceBufferState,
-                                       /*parent*/ nullptr);
+                                       /*parent*/ mRootSurfaceControl->getHandle());
         Transaction()
                 .setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
                 .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
@@ -1263,7 +1237,7 @@
         CallbackData callbackData;
         transactionCallback.getCallbackData(&callbackData);
         // capture screen and verify that it is red
-        ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
         ASSERT_NO_FATAL_FAILURE(
                 checkScreenCapture(255, 0, 0,
                                    {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -1323,43 +1297,6 @@
     ASSERT_EQ(queuesToNativeWindow, 1);
 }
 
-// Test a slow producer doesn't hold up a faster producer from the same client. Essentially tests
-// BBQ uses separate transaction queues.
-TEST_F(BLASTBufferQueueTest, OutOfOrderTransactionTest) {
-    sp<SurfaceControl> bgSurface =
-            mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceBufferState);
-    ASSERT_NE(nullptr, bgSurface.get());
-    Transaction t;
-    t.setLayerStack(bgSurface, ui::DEFAULT_LAYER_STACK)
-            .show(bgSurface)
-            .setDataspace(bgSurface, ui::Dataspace::V0_SRGB)
-            .setLayer(bgSurface, std::numeric_limits<int32_t>::max() - 1)
-            .apply();
-
-    BLASTBufferQueueHelper slowAdapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
-    sp<IGraphicBufferProducer> slowIgbProducer;
-    setUpProducer(slowAdapter, slowIgbProducer);
-    nsecs_t presentTimeDelay = std::chrono::nanoseconds(500ms).count();
-    queueBuffer(slowIgbProducer, 0 /* r */, 255 /* g */, 0 /* b */, presentTimeDelay);
-
-    BLASTBufferQueueHelper fastAdapter(bgSurface, mDisplayWidth, mDisplayHeight);
-    sp<IGraphicBufferProducer> fastIgbProducer;
-    setUpProducer(fastAdapter, fastIgbProducer);
-    uint8_t r = 255;
-    uint8_t g = 0;
-    uint8_t b = 0;
-    queueBuffer(fastIgbProducer, r, g, b, 0 /* presentTimeDelay */);
-    fastAdapter.waitForCallbacks();
-
-    // capture screen and verify that it is red
-    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
-
-    ASSERT_NO_FATAL_FAILURE(
-            checkScreenCapture(r, g, b,
-                               {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight / 2}));
-}
-
 TEST_F(BLASTBufferQueueTest, TransformHint) {
     // Transform hint is provided to BBQ via the surface control passed by WM
     mSurfaceControl->setTransformHint(ui::Transform::ROT_90);
@@ -1432,8 +1369,8 @@
         igbProducer->queueBuffer(slot, input, &qbOutput);
         ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
 
-        adapter.waitForCallbacks();
-        ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+        Transaction().apply(true /* synchronous */);
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
 
         switch (tr) {
             case ui::Transform::ROT_0:
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index d585881..17aa5f1 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -21,8 +21,10 @@
 #include "MockConsumer.h"
 
 #include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
 #include <gui/BufferQueue.h>
 #include <gui/IProducerListener.h>
+#include <gui/Surface.h>
 
 #include <ui/GraphicBuffer.h>
 
@@ -35,13 +37,18 @@
 
 #include <system/window.h>
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <future>
 #include <thread>
 
+#include <com_android_graphics_libgui_flags.h>
+
 using namespace std::chrono_literals;
 
 namespace android {
+using namespace com::android::graphics::libgui;
 
 class BufferQueueTest : public ::testing::Test {
 
@@ -1258,4 +1265,111 @@
     ASSERT_EQ(NO_INIT, mProducer->disconnect(NATIVE_WINDOW_API_CPU));
 }
 
+TEST_F(BufferQueueTest, TestBqSetFrameRateFlagBuildTimeIsSet) {
+    if (flags::bq_setframerate()) {
+        ASSERT_EQ(true, FLAG_BQ_SET_FRAME_RATE);
+    }
+}
+
+struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer {
+    BufferItemConsumerSetFrameRateListener(const sp<IGraphicBufferConsumer>& consumer)
+          : BufferItemConsumer(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 1) {}
+
+    MOCK_METHOD(void, onSetFrameRate, (float, int8_t, int8_t), (override));
+};
+
+TEST_F(BufferQueueTest, TestSetFrameRate) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<BufferItemConsumerSetFrameRateListener> bufferConsumer =
+            sp<BufferItemConsumerSetFrameRateListener>::make(consumer);
+
+    EXPECT_CALL(*bufferConsumer, onSetFrameRate(12.34f, 1, 0)).Times(1);
+    producer->setFrameRate(12.34f, 1, 0);
+}
+
+class Latch {
+public:
+    explicit Latch(int expected) : mExpected(expected) {}
+    Latch(const Latch&) = delete;
+    Latch& operator=(const Latch&) = delete;
+
+    void CountDown() {
+        std::unique_lock<std::mutex> lock(mLock);
+        mExpected--;
+        if (mExpected <= 0) {
+            mCV.notify_all();
+        }
+    }
+
+    void Wait() {
+        std::unique_lock<std::mutex> lock(mLock);
+        mCV.wait(lock, [&] { return mExpected == 0; });
+    }
+
+private:
+    int mExpected;
+    std::mutex mLock;
+    std::condition_variable mCV;
+};
+
+struct OneshotOnDequeuedListener final : public BufferItemConsumer::FrameAvailableListener {
+    OneshotOnDequeuedListener(std::function<void()>&& oneshot)
+          : mOneshotRunnable(std::move(oneshot)) {}
+
+    std::function<void()> mOneshotRunnable;
+
+    void run() {
+        if (mOneshotRunnable) {
+            mOneshotRunnable();
+            mOneshotRunnable = nullptr;
+        }
+    }
+
+    void onFrameDequeued(const uint64_t) override { run(); }
+
+    void onFrameAvailable(const BufferItem&) override {}
+};
+
+// See b/270004534
+TEST(BufferQueueThreading, TestProducerDequeueConsumerDestroy) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<BufferItemConsumer> bufferConsumer =
+            sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+    ASSERT_NE(nullptr, bufferConsumer.get());
+    sp<Surface> surface = sp<Surface>::make(producer);
+    native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+    native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+    Latch triggerDisconnect(1);
+    Latch resumeCallback(1);
+    auto luckyListener = sp<OneshotOnDequeuedListener>::make([&]() {
+        triggerDisconnect.CountDown();
+        resumeCallback.Wait();
+    });
+    bufferConsumer->setFrameAvailableListener(luckyListener);
+
+    std::future<void> disconnecter = std::async(std::launch::async, [&]() {
+        triggerDisconnect.Wait();
+        luckyListener = nullptr;
+        bufferConsumer = nullptr;
+        resumeCallback.CountDown();
+    });
+
+    std::future<void> render = std::async(std::launch::async, [=]() {
+        ANativeWindow_Buffer buffer;
+        surface->lock(&buffer, nullptr);
+        surface->unlockAndPost();
+    });
+
+    ASSERT_EQ(std::future_status::ready, render.wait_for(1s));
+    EXPECT_EQ(nullptr, luckyListener.get());
+    EXPECT_EQ(nullptr, bufferConsumer.get());
+}
+
 } // namespace android
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index 3949d70..29eeaa8 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -59,6 +59,7 @@
                  lastFrameTimelineOffset + 16);
 
     CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connected, 0);
+    CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connectionError, 4);
 
     CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, modeId, 0);
     CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, vsyncPeriod, 8);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 4d5bd5b..662e9fe 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -24,6 +24,7 @@
 
 #include <memory>
 
+#include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/keycodes.h>
 #include <android/native_window.h>
 
@@ -74,6 +75,26 @@
 static const int LAYER_BASE = INT32_MAX - 10;
 static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s;
 
+class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
+public:
+    binder::Status onWindowInfosReported() override {
+        std::lock_guard<std::mutex> lock{mMutex};
+        mWindowInfosReported = true;
+        mConditionVariable.notify_one();
+        return binder::Status::ok();
+    }
+
+    void wait() {
+        std::unique_lock<std::mutex> lock{mMutex};
+        mConditionVariable.wait(lock, [&] { return mWindowInfosReported; });
+    }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mConditionVariable;
+    bool mWindowInfosReported{false};
+};
+
 class InputSurface {
 public:
     InputSurface(const sp<SurfaceControl> &sc, int width, int height, bool noInputChannel = false) {
@@ -264,7 +285,10 @@
         t.setPosition(mSurfaceControl, x, y);
         t.setCrop(mSurfaceControl, crop);
         t.setAlpha(mSurfaceControl, 1);
-        t.apply(true);
+        auto reportedListener = sp<SynchronousWindowInfosReportedListener>::make();
+        t.addWindowInfosReportedListener(reportedListener);
+        t.apply();
+        reportedListener->wait();
     }
 
     void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) {
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
new file mode 100644
index 0000000..5fe22b0
--- /dev/null
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <gui/FrameRateUtils.h>
+#include <inttypes.h>
+#include <system/window.h>
+
+#include <com_android_graphics_libgui_flags.h>
+
+namespace android {
+using namespace com::android::graphics::libgui;
+
+TEST(FrameRateUtilsTest, ValidateFrameRate) {
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Privileged APIs.
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    constexpr bool kPrivileged = true;
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  kPrivileged));
+    EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  kPrivileged));
+
+    // Invalid frame rate.
+    EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Invalid compatibility.
+    EXPECT_FALSE(
+            ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Invalid change frame rate strategy.
+    if (flags::bq_setframerate()) {
+        EXPECT_FALSE(
+                ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, -1, ""));
+        EXPECT_FALSE(
+                ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, 2, ""));
+    }
+}
+
+} // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 6adcd96..e7b1232 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -879,10 +879,6 @@
         return binder::Status::ok();
     }
 
-    binder::Status getColorManagement(bool* /*outGetColorManagement*/) override {
-        return binder::Status::ok();
-    }
-
     binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override {
         return binder::Status::ok();
     }
@@ -993,12 +989,22 @@
         return binder::Status::ok();
     }
 
-    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*uids*/,
+    binder::Status enableRefreshRateOverlay(bool /*active*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setDebugFlash(int /*delay*/) override { return binder::Status::ok(); }
+
+    binder::Status scheduleComposite() override { return binder::Status::ok(); }
+
+    binder::Status scheduleCommit() override { return binder::Status::ok(); }
+
+    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*appIds*/,
                                             const std::vector<float>& /*thresholds*/) {
         return binder::Status::ok();
     }
 
-    binder::Status setSmallAreaDetectionThreshold(int32_t /*uid*/, float /*threshold*/) {
+    binder::Status setSmallAreaDetectionThreshold(int32_t /*appId*/, float /*threshold*/) {
         return binder::Status::ok();
     }
 
@@ -1030,6 +1036,10 @@
         return binder::Status::ok();
     }
 
+    binder::Status getSchedulingPolicy(gui::SchedulingPolicy*) override {
+        return binder::Status::ok();
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index 461fe4a..f2feaef 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -52,10 +52,7 @@
     i.layoutParamsFlags = WindowInfo::Flag::SLIPPERY;
     i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
     i.dispatchingTimeout = 12s;
-    i.frameLeft = 93;
-    i.frameTop = 34;
-    i.frameRight = 16;
-    i.frameBottom = 19;
+    i.frame = Rect(93, 34, 16, 19);
     i.surfaceInset = 17;
     i.globalScaleFactor = 0.3;
     i.alpha = 0.7;
@@ -85,10 +82,7 @@
     ASSERT_EQ(i.layoutParamsFlags, i2.layoutParamsFlags);
     ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
     ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
-    ASSERT_EQ(i.frameLeft, i2.frameLeft);
-    ASSERT_EQ(i.frameTop, i2.frameTop);
-    ASSERT_EQ(i.frameRight, i2.frameRight);
-    ASSERT_EQ(i.frameBottom, i2.frameBottom);
+    ASSERT_EQ(i.frame, i2.frame);
     ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
     ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
     ASSERT_EQ(i.alpha, i2.alpha);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 022dfad..69a4f0a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -33,6 +33,29 @@
     ],
 }
 
+/////////////////////////////////////////////////
+// flags
+/////////////////////////////////////////////////
+aconfig_declarations {
+    name: "aconfig_input_flags",
+    package: "com.android.input.flags",
+    srcs: ["input_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "aconfig_input_flags_c_lib",
+    aconfig_declarations: "aconfig_input_flags",
+    host_supported: true,
+    // Use the test version of the aconfig flag library by default to allow tests to set local
+    // overrides for flags, without having to link against a separate version of libinput or of this
+    // library. Bundling this library directly into libinput prevents us from having to add this
+    // library as a shared lib dependency everywhere where libinput is used.
+    test: true,
+    shared: {
+        enabled: false,
+    },
+}
+
 aidl_interface {
     name: "inputconstants",
     host_supported: true,
@@ -65,12 +88,38 @@
     bindgen_flags: [
         "--verbose",
         "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
+        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED",
+        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED",
+        "--allowlist-var=AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT",
+        "--allowlist-var=AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE",
         "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
         "--allowlist-var=AMOTION_EVENT_ACTION_UP",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
         "--allowlist-var=MAX_POINTER_ID",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_NAVIGATION",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_POSITION",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
+        "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
+        "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
+        "--allowlist-var=AINPUT_SOURCE_DPAD",
+        "--allowlist-var=AINPUT_SOURCE_GAMEPAD",
+        "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
+        "--allowlist-var=AINPUT_SOURCE_MOUSE",
+        "--allowlist-var=AINPUT_SOURCE_STYLUS",
+        "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
+        "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
+        "--allowlist-var=AINPUT_SOURCE_MOUSE_RELATIVE",
+        "--allowlist-var=AINPUT_SOURCE_TOUCHPAD",
+        "--allowlist-var=AINPUT_SOURCE_TOUCH_NAVIGATION",
+        "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
+        "--allowlist-var=AINPUT_SOURCE_HDMI",
+        "--allowlist-var=AINPUT_SOURCE_SENSOR",
+        "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
     ],
 
     static_libs: [
@@ -109,62 +158,6 @@
     ],
 }
 
-genrule {
-    name: "libinput_cxx_bridge_code",
-    tools: ["cxxbridge"],
-    cmd: "$(location cxxbridge) $(in) >> $(out)",
-    srcs: ["input_verifier.rs"],
-    out: ["inputverifier_generated.cpp"],
-}
-
-genrule {
-    name: "libinput_cxx_bridge_header",
-    tools: ["cxxbridge"],
-    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
-    srcs: ["input_verifier.rs"],
-    out: ["input_verifier.rs.h"],
-}
-
-rust_defaults {
-    name: "libinput_rust_defaults",
-    srcs: ["input_verifier.rs"],
-    host_supported: true,
-    rustlibs: [
-        "libbitflags",
-        "libcxx",
-        "libinput_bindgen",
-        "liblogger",
-        "liblog_rust",
-        "inputconstants-rust",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "liblog",
-    ],
-}
-
-rust_ffi_static {
-    name: "libinput_rust",
-    crate_name: "input",
-    defaults: ["libinput_rust_defaults"],
-}
-
-rust_test {
-    name: "libinput_rust_test",
-    defaults: ["libinput_rust_defaults"],
-    whole_static_libs: [
-        "libinput_from_rust_to_cpp",
-    ],
-    test_options: {
-        unit_test: true,
-    },
-    test_suites: ["device_tests"],
-    sanitize: {
-        hwaddress: true,
-    },
-}
-
 cc_library {
     name: "libinput",
     cpp_std: "c++20",
@@ -174,12 +167,18 @@
         "-Wextra",
         "-Werror",
         "-Wno-unused-parameter",
+        "-Wthread-safety",
+        "-Wshadow",
+        "-Wshadow-field-in-constructor-modified",
+        "-Wshadow-uncaptured-local",
+        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
     srcs: [
-        "FromRustToCpp.cpp",
+        "android/os/IInputFlinger.aidl",
         "Input.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
+        "InputTransport.cpp",
         "InputVerifier.cpp",
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
@@ -213,15 +212,16 @@
         "toolbox_input_labels",
     ],
 
-    generated_sources: ["libinput_cxx_bridge_code"],
-
     shared_libs: [
         "libbase",
+        "libbinder",
         "libcutils",
         "liblog",
         "libPlatformProperties",
         "libtinyxml2",
+        "libutils",
         "libvintf",
+        "server_configurable_flags",
     ],
 
     ldflags: [
@@ -236,15 +236,18 @@
 
     static_libs: [
         "inputconstants-cpp",
+        "libgui_window_info_static",
         "libui-types",
         "libtflite_static",
     ],
 
     whole_static_libs: [
-        "libinput_rust",
+        "aconfig_input_flags_c_lib",
+        "libinput_rust_ffi",
     ],
 
     export_static_lib_headers: [
+        "libgui_window_info_static",
         "libui-types",
     ],
 
@@ -255,11 +258,6 @@
 
     target: {
         android: {
-            srcs: [
-                "InputTransport.cpp",
-                "android/os/IInputFlinger.aidl",
-            ],
-
             export_shared_lib_headers: ["libbinder"],
 
             shared_libs: [
@@ -271,42 +269,15 @@
                 "android.os.statsbootstrap_aidl-cpp",
             ],
 
-            static_libs: [
-                "libgui_window_info_static",
-            ],
-
-            export_static_lib_headers: [
-                "libgui_window_info_static",
-            ],
-
             required: [
                 "motion_predictor_model_prebuilt",
                 "motion_predictor_model_config",
             ],
         },
         host: {
-            shared: {
-                enabled: false,
-            },
             include_dirs: [
                 "bionic/libc/kernel/android/uapi/",
                 "bionic/libc/kernel/uapi",
-                "frameworks/native/libs/arect/include",
-            ],
-        },
-        host_linux: {
-            srcs: [
-                "InputTransport.cpp",
-            ],
-            static_libs: [
-                "libgui_window_info_static",
-            ],
-            shared_libs: [
-                "libbinder",
-            ],
-
-            export_static_lib_headers: [
-                "libgui_window_info_static",
             ],
         },
     },
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 00925ba..bd5b67b 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -217,6 +217,19 @@
     return toolType == ToolType::STYLUS || toolType == ToolType::ERASER;
 }
 
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties) {
+    if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+        return false;
+    }
+    // Need at least one stylus pointer for this event to be considered a stylus event
+    for (const PointerProperties& pointerProperties : properties) {
+        if (isStylusToolType(pointerProperties.toolType)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
     return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
              event.getSource(), event.getDisplayId()},
@@ -497,19 +510,6 @@
     }
 }
 
-// --- PointerProperties ---
-
-bool PointerProperties::operator==(const PointerProperties& other) const {
-    return id == other.id
-            && toolType == other.toolType;
-}
-
-void PointerProperties::copyFrom(const PointerProperties& other) {
-    id = other.id;
-    toolType = other.toolType;
-}
-
-
 // --- MotionEvent ---
 
 void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index bade686..0e627e5 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -18,6 +18,7 @@
 
 #include <linux/input-event-codes.h>
 #include <linux/input.h>
+#include <strings.h>
 
 #define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
 #define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
@@ -429,7 +430,8 @@
     DEFINE_FLAG(VIRTUAL), \
     DEFINE_FLAG(FUNCTION), \
     DEFINE_FLAG(GESTURE), \
-    DEFINE_FLAG(WAKE)
+    DEFINE_FLAG(WAKE), \
+    DEFINE_FLAG(FALLBACK_USAGE_MAPPING)
 
 // clang-format on
 
@@ -523,6 +525,14 @@
     return labels->name != nullptr ? labels->name : std::to_string(value);
 }
 
+std::optional<int> getValue(const label* labels, const char* searchLabel) {
+    if (labels == nullptr) return {};
+    while (labels->name != nullptr && ::strcasecmp(labels->name, searchLabel) != 0) {
+        labels++;
+    }
+    return labels->name != nullptr ? std::make_optional(labels->value) : std::nullopt;
+}
+
 const label* getCodeLabelsForType(int32_t type) {
     switch (type) {
         case EV_SYN:
@@ -556,7 +566,7 @@
     if (type == EV_KEY) {
         return ev_key_value_labels;
     }
-    if (type == EV_MSC && code == ABS_MT_TOOL_TYPE) {
+    if (type == EV_ABS && code == ABS_MT_TOOL_TYPE) {
         return mt_tool_labels;
     }
     return nullptr;
@@ -572,4 +582,17 @@
     };
 }
 
+std::optional<int> InputEventLookup::getLinuxEvdevEventTypeByLabel(const char* label) {
+    return getValue(ev_labels, label);
+}
+
+std::optional<int> InputEventLookup::getLinuxEvdevEventCodeByLabel(int32_t type,
+                                                                   const char* label) {
+    return getValue(getCodeLabelsForType(type), label);
+}
+
+std::optional<int> InputEventLookup::getLinuxEvdevInputPropByLabel(const char* label) {
+    return getValue(input_prop_labels, label);
+}
+
 } // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 4d3d8bc..30cedb0 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -23,7 +23,11 @@
 #include <log/log.h>
 #include <utils/Trace.h>
 
+#include <com_android_input_flags.h>
 #include <input/InputTransport.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
 
 namespace {
 
@@ -75,10 +79,20 @@
 
 /**
  * Log debug messages about touch event resampling.
- * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG" (requires restart)
+ *
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
  */
-const bool DEBUG_RESAMPLING =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+bool debugResampling() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_TRANSPORT_RESAMPLING =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+                                          ANDROID_LOG_INFO);
+        return DEBUG_TRANSPORT_RESAMPLING;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
 
 } // namespace
 
@@ -128,7 +142,8 @@
  * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
  */
 static bool verifyEvents() {
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
+    return input_flags::enable_outbound_event_verification() ||
+            __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
 }
 
 template<typename T>
@@ -409,7 +424,7 @@
     setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
     setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
 
-    sp<IBinder> token = new BBinder();
+    sp<IBinder> token = sp<BBinder>::make();
 
     std::string serverChannelName = name + " (server)";
     android::base::unique_fd serverFd(sockets[0]);
@@ -422,6 +437,10 @@
 }
 
 status_t InputChannel::sendMessage(const InputMessage* msg) {
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
+                                ")",
+                                mName.c_str(), msg->header.seq, msg->header.type));
     const size_t msgLength = msg->size();
     InputMessage cleanMsg;
     msg->getSanitizedCopy(&cleanMsg);
@@ -453,12 +472,6 @@
     ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
              ftl::enum_string(msg->header.type).c_str());
 
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32 ")",
-                             mName.c_str(), msg->header.seq, msg->header.type);
-        ATRACE_NAME(message.c_str());
-    }
     return OK;
 }
 
@@ -494,8 +507,8 @@
 
     ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
              ftl::enum_string(msg->header.type).c_str());
-
     if (ATRACE_ENABLED()) {
+        // Add an additional trace point to include data about the received message.
         std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
                                            ", type=0x%" PRIx32 ")",
                                            mName.c_str(), msg->header.seq, msg->header.type);
@@ -568,13 +581,10 @@
                                          int32_t flags, int32_t keyCode, int32_t scanCode,
                                          int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                                          nsecs_t eventTime) {
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)",
-                             mChannel->getName().c_str(), KeyEvent::actionToString(action),
-                             KeyEvent::getLabel(keyCode));
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)",
+                                mChannel->getName().c_str(), KeyEvent::actionToString(action),
+                                KeyEvent::getLabel(keyCode)));
     ALOGD_IF(debugTransportPublisher(),
              "channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
              "action=%s, flags=0x%x, keyCode=%s, scanCode=%d, metaState=0x%x, repeatCount=%d,"
@@ -616,16 +626,14 @@
         const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
         uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords) {
-    if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
-                                           mChannel->getName().c_str(),
-                                           MotionEvent::actionToString(action).c_str());
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
+                                mChannel->getName().c_str(),
+                                MotionEvent::actionToString(action).c_str()));
     if (verifyEvents()) {
         Result<void> result =
-                mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
-                                               pointerCoords, flags);
+                mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+                                               pointerProperties, pointerCoords, flags);
         if (!result.ok()) {
             LOG(FATAL) << "Bad stream: " << result.error();
         }
@@ -692,19 +700,17 @@
     msg.body.motion.eventTime = eventTime;
     msg.body.motion.pointerCount = pointerCount;
     for (uint32_t i = 0; i < pointerCount; i++) {
-        msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
-        msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
+        msg.body.motion.pointers[i].properties = pointerProperties[i];
+        msg.body.motion.pointers[i].coords = pointerCoords[i];
     }
 
     return mChannel->sendMessage(&msg);
 }
 
 status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) {
-    if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
-                                           mChannel->getName().c_str(), toString(hasFocus));
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
+                                mChannel->getName().c_str(), toString(hasFocus)));
     ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: seq=%u, id=%d, hasFocus=%s",
              mChannel->getName().c_str(), __func__, seq, eventId, toString(hasFocus));
 
@@ -718,12 +724,9 @@
 
 status_t InputPublisher::publishCaptureEvent(uint32_t seq, int32_t eventId,
                                              bool pointerCaptureEnabled) {
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("publishCaptureEvent(inputChannel=%s, pointerCaptureEnabled=%s)",
-                             mChannel->getName().c_str(), toString(pointerCaptureEnabled));
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("publishCaptureEvent(inputChannel=%s, pointerCaptureEnabled=%s)",
+                                mChannel->getName().c_str(), toString(pointerCaptureEnabled)));
     ALOGD_IF(debugTransportPublisher(),
              "channel '%s' publisher ~ %s: seq=%u, id=%d, pointerCaptureEnabled=%s",
              mChannel->getName().c_str(), __func__, seq, eventId, toString(pointerCaptureEnabled));
@@ -738,12 +741,9 @@
 
 status_t InputPublisher::publishDragEvent(uint32_t seq, int32_t eventId, float x, float y,
                                           bool isExiting) {
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("publishDragEvent(inputChannel=%s, x=%f, y=%f, isExiting=%s)",
-                             mChannel->getName().c_str(), x, y, toString(isExiting));
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("publishDragEvent(inputChannel=%s, x=%f, y=%f, isExiting=%s)",
+                                mChannel->getName().c_str(), x, y, toString(isExiting)));
     ALOGD_IF(debugTransportPublisher(),
              "channel '%s' publisher ~ %s: seq=%u, id=%d, x=%f, y=%f, isExiting=%s",
              mChannel->getName().c_str(), __func__, seq, eventId, x, y, toString(isExiting));
@@ -759,12 +759,9 @@
 }
 
 status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bool isInTouchMode) {
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
-                             mChannel->getName().c_str(), toString(isInTouchMode));
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
+                                mChannel->getName().c_str(), toString(isInTouchMode)));
     ALOGD_IF(debugTransportPublisher(),
              "channel '%s' publisher ~ %s: seq=%u, id=%d, isInTouchMode=%s",
              mChannel->getName().c_str(), __func__, seq, eventId, toString(isInTouchMode));
@@ -781,8 +778,10 @@
     InputMessage msg;
     status_t result = mChannel->receiveMessage(&msg);
     if (result) {
-        ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: %s",
-                 mChannel->getName().c_str(), __func__, strerror(result));
+        if (debugTransportPublisher() && result != WOULD_BLOCK) {
+            LOG(INFO) << "channel '" << mChannel->getName() << "' publisher ~ " << __func__ << ": "
+                      << strerror(result);
+        }
         return android::base::Error(result);
     }
     if (msg.header.type == InputMessage::Type::FINISHED) {
@@ -1158,7 +1157,7 @@
                     state.recentCoordinatesAreIdentical(id)) {
                 PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
                 const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
-                ALOGD_IF(DEBUG_RESAMPLING, "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+                ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
                          resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
                          msgCoords.getY());
                 msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
@@ -1181,13 +1180,13 @@
 
     ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
     if (index < 0) {
-        ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no touch state for device.");
+        ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
         return;
     }
 
     TouchState& touchState = mTouchStates[index];
     if (touchState.historySize < 1) {
-        ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no history for device.");
+        ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
         return;
     }
 
@@ -1197,7 +1196,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         uint32_t id = event->getPointerId(i);
         if (!current->idBits.hasBit(id)) {
-            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, missing id %d", id);
+            ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
             return;
         }
     }
@@ -1213,7 +1212,7 @@
         other = &future;
         nsecs_t delta = future.eventTime - current->eventTime;
         if (delta < RESAMPLE_MIN_DELTA) {
-            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.",
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
                      delta);
             return;
         }
@@ -1224,17 +1223,17 @@
         other = touchState.getHistory(1);
         nsecs_t delta = current->eventTime - other->eventTime;
         if (delta < RESAMPLE_MIN_DELTA) {
-            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.",
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
                      delta);
             return;
         } else if (delta > RESAMPLE_MAX_DELTA) {
-            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too large: %" PRId64 " ns.",
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
                      delta);
             return;
         }
         nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
         if (sampleTime > maxPredict) {
-            ALOGD_IF(DEBUG_RESAMPLING,
+            ALOGD_IF(debugResampling(),
                      "Sample time is too far in the future, adjusting prediction "
                      "from %" PRId64 " to %" PRId64 " ns.",
                      sampleTime - current->eventTime, maxPredict - current->eventTime);
@@ -1242,7 +1241,7 @@
         }
         alpha = float(current->eventTime - sampleTime) / delta;
     } else {
-        ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, insufficient data.");
+        ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
         return;
     }
 
@@ -1270,27 +1269,27 @@
             // We know here that the coordinates for the pointer haven't changed because we
             // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
             // lastResample in place becasue the mapping from pointer ID to index may have changed.
-            touchState.lastResample.pointers[i].copyFrom(oldLastResample.getPointerById(id));
+            touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
             continue;
         }
 
         PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
         const PointerCoords& currentCoords = current->getPointerById(id);
-        resampledCoords.copyFrom(currentCoords);
+        resampledCoords = currentCoords;
+        resampledCoords.isResampled = true;
         if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
             const PointerCoords& otherCoords = other->getPointerById(id);
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
                                          lerp(currentCoords.getX(), otherCoords.getX(), alpha));
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
                                          lerp(currentCoords.getY(), otherCoords.getY(), alpha));
-            resampledCoords.isResampled = true;
-            ALOGD_IF(DEBUG_RESAMPLING,
+            ALOGD_IF(debugResampling(),
                      "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
                      "other (%0.3f, %0.3f), alpha %0.3f",
                      id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
                      currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
         } else {
-            ALOGD_IF(DEBUG_RESAMPLING, "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
+            ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
                      resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
                      currentCoords.getY());
         }
@@ -1454,8 +1453,8 @@
     PointerProperties pointerProperties[pointerCount];
     PointerCoords pointerCoords[pointerCount];
     for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties);
-        pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+        pointerProperties[i] = msg->body.motion.pointers[i].properties;
+        pointerCoords[i] = msg->body.motion.pointers[i].coords;
     }
 
     ui::Transform transform;
@@ -1484,7 +1483,7 @@
     uint32_t pointerCount = msg->body.motion.pointerCount;
     PointerCoords pointerCoords[pointerCount];
     for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+        pointerCoords[i] = msg->body.motion.pointers[i].coords;
     }
 
     event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index 9745e89..cec2445 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -18,12 +18,14 @@
 
 #include <android-base/logging.h>
 #include <input/InputVerifier.h>
-#include "input_verifier.rs.h"
+#include "input_cxx_bridge.rs.h"
 
 using android::base::Error;
 using android::base::Result;
 using android::input::RustPointerProperties;
 
+using DeviceId = int32_t;
+
 namespace android {
 
 // --- InputVerifier ---
@@ -31,7 +33,8 @@
 InputVerifier::InputVerifier(const std::string& name)
       : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
 
-Result<void> InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
+Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
+                                            uint32_t pointerCount,
                                             const PointerProperties* pointerProperties,
                                             const PointerCoords* pointerCoords, int32_t flags) {
     std::vector<RustPointerProperties> rpp;
@@ -40,8 +43,8 @@
     }
     rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
     rust::String errorMessage =
-            android::input::verifier::process_movement(*mVerifier, deviceId, action, properties,
-                                                       flags);
+            android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
+                                                       properties, static_cast<uint32_t>(flags));
     if (errorMessage.empty()) {
         return {};
     } else {
@@ -49,4 +52,8 @@
     }
 }
 
+void InputVerifier::resetDevice(DeviceId deviceId) {
+    android::input::verifier::reset_device(*mVerifier, deviceId);
+}
+
 } // namespace android
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index ddc9ea4..3c1ae3e 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -250,7 +250,7 @@
 std::vector<int32_t> KeyLayoutMap::findUsageCodesForKey(int32_t keyCode) const {
     std::vector<int32_t> usageCodes;
     for (const auto& [usageCode, key] : mKeysByUsageCode) {
-        if (keyCode == key.keyCode) {
+        if (keyCode == key.keyCode && !(key.flags & POLICY_FLAG_FALLBACK_USAGE_MAPPING)) {
             usageCodes.push_back(usageCode);
         }
     }
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 5736ad7..412931b 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -25,9 +25,9 @@
 #include <string>
 #include <vector>
 
+#include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <android/input.h>
-#include <log/log.h>
 
 #include <attestation/HmacKeyManager.h>
 #include <ftl/enum.h>
@@ -205,6 +205,13 @@
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, predictedPoint.x);
         coords.setAxisValue(AMOTION_EVENT_AXIS_Y, predictedPoint.y);
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+        // Copy forward tilt and orientation from the last event until they are predicted
+        // (b/291789258).
+        coords.setAxisValue(AMOTION_EVENT_AXIS_TILT,
+                            event.getAxisValue(AMOTION_EVENT_AXIS_TILT, 0));
+        coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                            event.getRawPointerCoords(0)->getAxisValue(
+                                    AMOTION_EVENT_AXIS_ORIENTATION));
 
         predictionTime += mModel->config().predictionInterval;
         if (i == 0) {
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index 5720099..c835a08 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -37,7 +37,7 @@
     reset();
 }
 
-VelocityControlParameters& VelocityControl::getParameters() {
+const VelocityControlParameters& VelocityControl::getParameters() const{
     return mParameters;
 }
 
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 078109a..116b778 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -17,14 +17,13 @@
 #define LOG_TAG "VelocityTracker"
 
 #include <android-base/logging.h>
-#include <array>
 #include <ftl/enum.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <math.h>
+#include <array>
 #include <optional>
 
-#include <android-base/stringprintf.h>
 #include <input/PrintTools.h>
 #include <input/VelocityTracker.h>
 #include <utils/BitSet.h>
@@ -58,6 +57,9 @@
 // Nanoseconds per milliseconds.
 static const nsecs_t NANOS_PER_MS = 1000000;
 
+// Seconds per nanosecond.
+static const float SECONDS_PER_NANO = 1E-9;
+
 // All axes supported for velocity tracking, mapped to their default strategies.
 // Although other strategies are available for testing and comparison purposes,
 // the default strategy is the one that applications will actually use.  Be very careful
@@ -238,6 +240,11 @@
 
 void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
                                   float position) {
+    if (pointerId < 0 || pointerId > MAX_POINTER_ID) {
+        LOG(FATAL) << "Invalid pointer ID " << pointerId << " for axis "
+                   << MotionEvent::getLabel(axis);
+    }
+
     if (mCurrentPointerIdBits.hasBit(pointerId) &&
         std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
         ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
@@ -261,16 +268,10 @@
     mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
 
     if (DEBUG_VELOCITY) {
-        ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32
-              ", activePointerId=%s",
-              eventTime, pointerId, toString(mActivePointerId).c_str());
-
-        std::optional<Estimator> estimator = getEstimator(axis, pointerId);
-        ALOGD("  %d: axis=%d, position=%0.3f, "
-              "estimator (degree=%d, coeff=%s, confidence=%f)",
-              pointerId, axis, position, int((*estimator).degree),
-              vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(),
-              (*estimator).confidence);
+        LOG(INFO) << "VelocityTracker: addMovement axis=" << MotionEvent::getLabel(axis)
+                  << ", eventTime=" << eventTime << ", pointerId=" << pointerId
+                  << ", activePointerId=" << toString(mActivePointerId) << ", position=" << position
+                  << ", velocity=" << toString(getVelocity(axis, pointerId));
     }
 }
 
@@ -346,9 +347,9 @@
 }
 
 std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
-    std::optional<Estimator> estimator = getEstimator(axis, pointerId);
-    if (estimator && (*estimator).degree >= 1) {
-        return (*estimator).coeff[1];
+    const auto& it = mConfiguredStrategies.find(axis);
+    if (it != mConfiguredStrategies.end()) {
+        return it->second->getVelocity(pointerId);
     }
     return {};
 }
@@ -371,56 +372,52 @@
     return computedVelocity;
 }
 
-std::optional<VelocityTracker::Estimator> VelocityTracker::getEstimator(int32_t axis,
-                                                                        int32_t pointerId) const {
-    const auto& it = mConfiguredStrategies.find(axis);
-    if (it == mConfiguredStrategies.end()) {
-        return std::nullopt;
+AccumulatingVelocityTrackerStrategy::AccumulatingVelocityTrackerStrategy(
+        nsecs_t horizonNanos, bool maintainHorizonDuringAdd)
+      : mHorizonNanos(horizonNanos), mMaintainHorizonDuringAdd(maintainHorizonDuringAdd) {}
+
+void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+    mMovements.erase(pointerId);
+}
+
+void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+                                                      float position) {
+    auto [ringBufferIt, _] = mMovements.try_emplace(pointerId, HISTORY_SIZE);
+    RingBuffer<Movement>& movements = ringBufferIt->second;
+    const size_t size = movements.size();
+
+    if (size != 0 && movements[size - 1].eventTime == eventTime) {
+        // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
+        // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
+        // the new pointer. If the eventtimes for both events are identical, just update the data
+        // for this time (i.e. pop out the last element, and insert the updated movement).
+        // We only compare against the last value, as it is likely that addMovement is called
+        // in chronological order as events occur.
+        movements.popBack();
     }
-    return it->second->getEstimator(pointerId);
+
+    movements.pushBack({eventTime, position});
+
+    // Clear movements that do not fall within `mHorizonNanos` of the latest movement.
+    // Note that, if in the future we decide to use more movements (i.e. increase HISTORY_SIZE),
+    // we can consider making this step binary-search based, which will give us some improvement.
+    if (mMaintainHorizonDuringAdd) {
+        while (eventTime - movements[0].eventTime > mHorizonNanos) {
+            movements.popFront();
+        }
+    }
 }
 
 // --- LeastSquaresVelocityTrackerStrategy ---
 
 LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
                                                                          Weighting weighting)
-      : mDegree(degree), mWeighting(weighting) {}
+      : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+                                            true /*maintainHorizonDuringAdd*/),
+        mDegree(degree),
+        mWeighting(weighting) {}
 
-LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
-}
-
-void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
-    mIndex.erase(pointerId);
-    mMovements.erase(pointerId);
-}
-
-void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
-                                                      float position) {
-    // If data for this pointer already exists, we have a valid entry at the position of
-    // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
-    // to the next position in the circular buffer and write the new Movement there. Otherwise,
-    // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
-    // for this pointer and write to the first position.
-    auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
-    auto [indexIt, _] = mIndex.insert({pointerId, 0});
-    size_t& index = indexIt->second;
-    if (!inserted && movementIt->second[index].eventTime != eventTime) {
-        // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
-        // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
-        // the new pointer. If the eventtimes for both events are identical, just update the data
-        // for this time.
-        // We only compare against the last value, as it is likely that addMovement is called
-        // in chronological order as events occur.
-        index++;
-    }
-    if (index == HISTORY_SIZE) {
-        index = 0;
-    }
-
-    Movement& movement = movementIt->second[index];
-    movement.eventTime = eventTime;
-    movement.position = position;
-}
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
 
 /**
  * Solves a linear least squares problem to obtain a N degree polynomial that fits
@@ -471,10 +468,9 @@
  * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
  * http://en.wikipedia.org/wiki/Gram-Schmidt
  */
-static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
-                              const std::vector<float>& w, uint32_t n,
-                              std::array<float, VelocityTracker::Estimator::MAX_DEGREE + 1>& outB,
-                              float* outDet) {
+static std::optional<float> solveLeastSquares(const std::vector<float>& x,
+                                              const std::vector<float>& y,
+                                              const std::vector<float>& w, uint32_t n) {
     const size_t m = x.size();
 
     ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
@@ -512,7 +508,7 @@
         if (norm < 0.000001f) {
             // vectors are linearly dependent or zero so no solution
             ALOGD_IF(DEBUG_STRATEGY, "  - no solution, norm=%f", norm);
-            return false;
+            return {};
         }
 
         float invNorm = 1.0f / norm;
@@ -546,6 +542,7 @@
     for (uint32_t h = 0; h < m; h++) {
         wy[h] = y[h] * w[h];
     }
+    std::array<float, VelocityTracker::MAX_DEGREE + 1> outB;
     for (uint32_t i = n; i != 0; ) {
         i--;
         outB[i] = vectorDot(&q[i][0], wy, m);
@@ -567,42 +564,46 @@
     }
     ymean /= m;
 
-    float sserr = 0;
-    float sstot = 0;
-    for (uint32_t h = 0; h < m; h++) {
-        float err = y[h] - outB[0];
-        float term = 1;
-        for (uint32_t i = 1; i < n; i++) {
-            term *= x[h];
-            err -= term * outB[i];
+    if (DEBUG_STRATEGY) {
+        float sserr = 0;
+        float sstot = 0;
+        for (uint32_t h = 0; h < m; h++) {
+            float err = y[h] - outB[0];
+            float term = 1;
+            for (uint32_t i = 1; i < n; i++) {
+                term *= x[h];
+                err -= term * outB[i];
+            }
+            sserr += w[h] * w[h] * err * err;
+            float var = y[h] - ymean;
+            sstot += w[h] * w[h] * var * var;
         }
-        sserr += w[h] * w[h] * err * err;
-        float var = y[h] - ymean;
-        sstot += w[h] * w[h] * var * var;
+        ALOGD("  - sserr=%f", sserr);
+        ALOGD("  - sstot=%f", sstot);
     }
-    *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
 
-    ALOGD_IF(DEBUG_STRATEGY, "  - sserr=%f", sserr);
-    ALOGD_IF(DEBUG_STRATEGY, "  - sstot=%f", sstot);
-    ALOGD_IF(DEBUG_STRATEGY, "  - det=%f", *outDet);
-
-    return true;
+    return outB[1];
 }
 
 /*
  * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
  * the default implementation
  */
-static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
-        const std::vector<float>& x, const std::vector<float>& y) {
-    const size_t count = x.size();
-    LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes");
-    // Solving y = a*x^2 + b*x + c
+std::optional<float> LeastSquaresVelocityTrackerStrategy::solveUnweightedLeastSquaresDeg2(
+        const RingBuffer<Movement>& movements) const {
+    // Solving y = a*x^2 + b*x + c, where
+    //      - "x" is age (i.e. duration since latest movement) of the movemnets
+    //      - "y" is positions of the movements.
     float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
 
+    const size_t count = movements.size();
+    const Movement& newestMovement = movements[count - 1];
     for (size_t i = 0; i < count; i++) {
-        float xi = x[i];
-        float yi = y[i];
+        const Movement& movement = movements[i];
+        nsecs_t age = newestMovement.eventTime - movement.eventTime;
+        float xi = -age * SECONDS_PER_NANO;
+        float yi = movement.position;
+
         float xi2 = xi*xi;
         float xi3 = xi2*xi;
         float xi4 = xi3*xi;
@@ -629,124 +630,68 @@
         ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
         return std::nullopt;
     }
-    // Compute a
-    float numerator = Sx2y*Sxx - Sxy*Sxx2;
-    float a = numerator / denominator;
 
-    // Compute b
-    numerator = Sxy*Sx2x2 - Sx2y*Sxx2;
-    float b = numerator / denominator;
-
-    // Compute c
-    float c = syi/count - b * sxi/count - a * sxi2/count;
-
-    return std::make_optional(std::array<float, 3>({c, b, a}));
+    return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
 }
 
-std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::getEstimator(
-        int32_t pointerId) const {
+std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
     const auto movementIt = mMovements.find(pointerId);
     if (movementIt == mMovements.end()) {
         return std::nullopt; // no data
     }
+
+    const RingBuffer<Movement>& movements = movementIt->second;
+    const size_t size = movements.size();
+    if (size == 0) {
+        return std::nullopt; // no data
+    }
+
+    uint32_t degree = mDegree;
+    if (degree > size - 1) {
+        degree = size - 1;
+    }
+
+    if (degree <= 0) {
+        return std::nullopt;
+    }
+
+    if (degree == 2 && mWeighting == Weighting::NONE) {
+        // Optimize unweighted, quadratic polynomial fit
+        return solveUnweightedLeastSquaresDeg2(movements);
+    }
+
     // Iterate over movement samples in reverse time order and collect samples.
     std::vector<float> positions;
     std::vector<float> w;
     std::vector<float> time;
 
-    uint32_t index = mIndex.at(pointerId);
-    const Movement& newestMovement = movementIt->second[index];
-    do {
-        const Movement& movement = movementIt->second[index];
-
+    const Movement& newestMovement = movements[size - 1];
+    for (ssize_t i = size - 1; i >= 0; i--) {
+        const Movement& movement = movements[i];
         nsecs_t age = newestMovement.eventTime - movement.eventTime;
-        if (age > HORIZON) {
-            break;
-        }
-        if (movement.eventTime == 0 && index != 0) {
-            // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's
-            // possible that not all entries are valid. We use a time=0 as a signal for those
-            // uninitialized values. If we encounter a time of 0 in a position
-            // that's > 0, it means that we hit the block where the data wasn't initialized.
-            // We still don't know whether the value at index=0, with eventTime=0 is valid.
-            // However, that's only possible when the value is by itself. So there's no hard in
-            // processing it anyways, since the velocity for a single point is zero, and this
-            // situation will only be encountered in artificial circumstances (in tests).
-            // In practice, time will never be 0.
-            break;
-        }
         positions.push_back(movement.position);
-        w.push_back(chooseWeight(pointerId, index));
+        w.push_back(chooseWeight(pointerId, i));
         time.push_back(-age * 0.000000001f);
-        index = (index == 0 ? HISTORY_SIZE : index) - 1;
-    } while (positions.size() < HISTORY_SIZE);
-
-    const size_t m = positions.size();
-    if (m == 0) {
-        return std::nullopt; // no data
     }
 
-    // Calculate a least squares polynomial fit.
-    uint32_t degree = mDegree;
-    if (degree > m - 1) {
-        degree = m - 1;
-    }
-
-    if (degree == 2 && mWeighting == Weighting::NONE) {
-        // Optimize unweighted, quadratic polynomial fit
-        std::optional<std::array<float, 3>> coeff =
-                solveUnweightedLeastSquaresDeg2(time, positions);
-        if (coeff) {
-            VelocityTracker::Estimator estimator;
-            estimator.time = newestMovement.eventTime;
-            estimator.degree = 2;
-            estimator.confidence = 1;
-            for (size_t i = 0; i <= estimator.degree; i++) {
-                estimator.coeff[i] = (*coeff)[i];
-            }
-            return estimator;
-        }
-    } else if (degree >= 1) {
-        // General case for an Nth degree polynomial fit
-        float det;
-        uint32_t n = degree + 1;
-        VelocityTracker::Estimator estimator;
-        if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) {
-            estimator.time = newestMovement.eventTime;
-            estimator.degree = degree;
-            estimator.confidence = det;
-
-            ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f",
-                     int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(),
-                     estimator.confidence);
-
-            return estimator;
-        }
-    }
-
-    // No velocity data available for this pointer, but we do have its current position.
-    VelocityTracker::Estimator estimator;
-    estimator.coeff[0] = positions[0];
-    estimator.time = newestMovement.eventTime;
-    estimator.degree = 0;
-    estimator.confidence = 1;
-    return estimator;
+    // General case for an Nth degree polynomial fit
+    return solveLeastSquares(time, positions, w, degree + 1);
 }
 
 float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
-    const std::array<Movement, HISTORY_SIZE>& movements = mMovements.at(pointerId);
+    const RingBuffer<Movement>& movements = mMovements.at(pointerId);
+    const size_t size = movements.size();
     switch (mWeighting) {
         case Weighting::DELTA: {
             // Weight points based on how much time elapsed between them and the next
             // point so that points that "cover" a shorter time span are weighed less.
             //   delta  0ms: 0.5
             //   delta 10ms: 1.0
-            if (index == mIndex.at(pointerId)) {
+            if (index == size - 1) {
                 return 1.0f;
             }
-            uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
             float deltaMillis =
-                    (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f;
+                    (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f;
             if (deltaMillis < 0) {
                 return 0.5f;
             }
@@ -763,8 +708,7 @@
             //   age 50ms: 1.0
             //   age 60ms: 0.5
             float ageMillis =
-                    (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
-                    0.000001f;
+                    (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
             if (ageMillis < 0) {
                 return 0.5f;
             }
@@ -786,8 +730,7 @@
             //   age  50ms: 1.0
             //   age 100ms: 0.5
             float ageMillis =
-                    (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
-                    0.000001f;
+                    (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
             if (ageMillis < 50) {
                 return 1.0f;
             }
@@ -827,13 +770,9 @@
     mPointerIdBits.markBit(pointerId);
 }
 
-std::optional<VelocityTracker::Estimator> IntegratingVelocityTrackerStrategy::getEstimator(
-        int32_t pointerId) const {
+std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
     if (mPointerIdBits.hasBit(pointerId)) {
-        const State& state = mPointerState[pointerId];
-        VelocityTracker::Estimator estimator;
-        populateEstimator(state, &estimator);
-        return estimator;
+        return mPointerState[pointerId].vel;
     }
 
     return std::nullopt;
@@ -883,77 +822,39 @@
     state.pos = pos;
 }
 
-void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
-        VelocityTracker::Estimator* outEstimator) const {
-    outEstimator->time = state.updateTime;
-    outEstimator->confidence = 1.0f;
-    outEstimator->degree = state.degree;
-    outEstimator->coeff[0] = state.pos;
-    outEstimator->coeff[1] = state.vel;
-    outEstimator->coeff[2] = state.accel / 2;
-}
-
-
 // --- LegacyVelocityTrackerStrategy ---
 
-LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {}
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy()
+      : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+                                            false /*maintainHorizonDuringAdd*/) {}
 
 LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
 }
 
-void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
-    mIndex.erase(pointerId);
-    mMovements.erase(pointerId);
-}
-
-void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
-                                                float position) {
-    // If data for this pointer already exists, we have a valid entry at the position of
-    // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
-    // to the next position in the circular buffer and write the new Movement there. Otherwise,
-    // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
-    // for this pointer and write to the first position.
-    auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
-    auto [indexIt, _] = mIndex.insert({pointerId, 0});
-    size_t& index = indexIt->second;
-    if (!inserted && movementIt->second[index].eventTime != eventTime) {
-        // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
-        // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
-        // the new pointer. If the eventtimes for both events are identical, just update the data
-        // for this time.
-        // We only compare against the last value, as it is likely that addMovement is called
-        // in chronological order as events occur.
-        index++;
-    }
-    if (index == HISTORY_SIZE) {
-        index = 0;
-    }
-
-    Movement& movement = movementIt->second[index];
-    movement.eventTime = eventTime;
-    movement.position = position;
-}
-
-std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEstimator(
-        int32_t pointerId) const {
+std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
     const auto movementIt = mMovements.find(pointerId);
     if (movementIt == mMovements.end()) {
         return std::nullopt; // no data
     }
-    const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)];
+
+    const RingBuffer<Movement>& movements = movementIt->second;
+    const size_t size = movements.size();
+    if (size == 0) {
+        return std::nullopt; // no data
+    }
+
+    const Movement& newestMovement = movements[size - 1];
 
     // Find the oldest sample that contains the pointer and that is not older than HORIZON.
     nsecs_t minTime = newestMovement.eventTime - HORIZON;
-    uint32_t oldestIndex = mIndex.at(pointerId);
-    uint32_t numTouches = 1;
-    do {
-        uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
-        const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex];
+    uint32_t oldestIndex = size - 1;
+    for (ssize_t i = size - 1; i >= 0; i--) {
+        const Movement& nextOldestMovement = movements[i];
         if (nextOldestMovement.eventTime < minTime) {
             break;
         }
-        oldestIndex = nextOldestIndex;
-    } while (++numTouches < HISTORY_SIZE);
+        oldestIndex = i;
+    }
 
     // Calculate an exponentially weighted moving average of the velocity estimate
     // at different points in time measured relative to the oldest sample.
@@ -967,17 +868,13 @@
     // 16ms apart but some consecutive samples could be only 0.5sm apart because
     // the hardware or driver reports them irregularly or in bursts.
     float accumV = 0;
-    uint32_t index = oldestIndex;
     uint32_t samplesUsed = 0;
-    const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex];
+    const Movement& oldestMovement = movements[oldestIndex];
     float oldestPosition = oldestMovement.position;
     nsecs_t lastDuration = 0;
 
-    while (numTouches-- > 1) {
-        if (++index == HISTORY_SIZE) {
-            index = 0;
-        }
-        const Movement& movement = mMovements.at(pointerId)[index];
+    for (size_t i = oldestIndex; i < size; i++) {
+        const Movement& movement = movements[i];
         nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
 
         // If the duration between samples is small, we may significantly overestimate
@@ -993,62 +890,22 @@
         }
     }
 
-    // Report velocity.
-    float newestPosition = newestMovement.position;
-    VelocityTracker::Estimator estimator;
-    estimator.time = newestMovement.eventTime;
-    estimator.confidence = 1;
-    estimator.coeff[0] = newestPosition;
     if (samplesUsed) {
-        estimator.coeff[1] = accumV;
-        estimator.degree = 1;
-    } else {
-        estimator.degree = 0;
+        return accumV;
     }
-    return estimator;
+    return std::nullopt;
 }
 
 // --- ImpulseVelocityTrackerStrategy ---
 
 ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
-      : mDeltaValues(deltaValues) {}
+      : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+                                            true /*maintainHorizonDuringAdd*/),
+        mDeltaValues(deltaValues) {}
 
 ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
 }
 
-void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
-    mIndex.erase(pointerId);
-    mMovements.erase(pointerId);
-}
-
-void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
-                                                 float position) {
-    // If data for this pointer already exists, we have a valid entry at the position of
-    // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
-    // to the next position in the circular buffer and write the new Movement there. Otherwise,
-    // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
-    // for this pointer and write to the first position.
-    auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
-    auto [indexIt, _] = mIndex.insert({pointerId, 0});
-    size_t& index = indexIt->second;
-    if (!inserted && movementIt->second[index].eventTime != eventTime) {
-        // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
-        // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
-        // the new pointer. If the eventtimes for both events are identical, just update the data
-        // for this time.
-        // We only compare against the last value, as it is likely that addMovement is called
-        // in chronological order as events occur.
-        index++;
-    }
-    if (index == HISTORY_SIZE) {
-        index = 0;
-    }
-
-    Movement& movement = movementIt->second[index];
-    movement.eventTime = eventTime;
-    movement.position = position;
-}
-
 /**
  * Calculate the total impulse provided to the screen and the resulting velocity.
  *
@@ -1123,112 +980,44 @@
     return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
 }
 
-static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count,
-                                      bool deltaValues) {
-    // The input should be in reversed time order (most recent sample at index i=0)
-    // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function
-    static constexpr float SECONDS_PER_NANO = 1E-9;
-
-    if (count < 2) {
-        return 0; // if 0 or 1 points, velocity is zero
-    }
-    if (t[1] > t[0]) { // Algorithm will still work, but not perfectly
-        ALOGE("Samples provided to calculateImpulseVelocity in the wrong order");
-    }
-
-    // If the data values are delta values, we do not have to calculate deltas here.
-    // We can use the delta values directly, along with the calculated time deltas.
-    // Since the data value input is in reversed time order:
-    //      [a] for non-delta inputs, instantenous velocity = (x[i] - x[i-1])/(t[i] - t[i-1])
-    //      [b] for delta inputs, instantenous velocity = -x[i-1]/(t[i] - t[i - 1])
-    // e.g., let the non-delta values are: V = [2, 3, 7], the equivalent deltas are D = [2, 1, 4].
-    // Since the input is in reversed time order, the input values for this function would be
-    // V'=[7, 3, 2] and D'=[4, 1, 2] for the non-delta and delta values, respectively.
-    //
-    // The equivalent of {(V'[2] - V'[1]) = 2 - 3 = -1} would be {-D'[1] = -1}
-    // Similarly, the equivalent of {(V'[1] - V'[0]) = 3 - 7 = -4} would be {-D'[0] = -4}
-
-    if (count == 2) { // if 2 points, basic linear calculation
-        if (t[1] == t[0]) {
-            ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]);
-            return 0;
-        }
-        const float deltaX = deltaValues ? -x[0] : x[1] - x[0];
-        return deltaX / (SECONDS_PER_NANO * (t[1] - t[0]));
-    }
-    // Guaranteed to have at least 3 points here
-    float work = 0;
-    for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time
-        if (t[i] == t[i-1]) {
-            ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]);
-            continue;
-        }
-        float vprev = kineticEnergyToVelocity(work); // v[i-1]
-        const float deltaX = deltaValues ? -x[i-1] : x[i] - x[i-1];
-        float vcurr = deltaX / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i]
-        work += (vcurr - vprev) * fabsf(vcurr);
-        if (i == count - 1) {
-            work *= 0.5; // initial condition, case 2) above
-        }
-    }
-    return kineticEnergyToVelocity(work);
-}
-
-std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEstimator(
-        int32_t pointerId) const {
+std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
     const auto movementIt = mMovements.find(pointerId);
     if (movementIt == mMovements.end()) {
         return std::nullopt; // no data
     }
 
-    // Iterate over movement samples in reverse time order and collect samples.
-    float positions[HISTORY_SIZE];
-    nsecs_t time[HISTORY_SIZE];
-    size_t m = 0; // number of points that will be used for fitting
-    size_t index = mIndex.at(pointerId);
-    const Movement& newestMovement = movementIt->second[index];
-    do {
-        const Movement& movement = movementIt->second[index];
-
-        nsecs_t age = newestMovement.eventTime - movement.eventTime;
-        if (age > HORIZON) {
-            break;
-        }
-        if (movement.eventTime == 0 && index != 0) {
-            // All eventTime's are initialized to 0. If we encounter a time of 0 in a position
-            // that's >0, it means that we hit the block where the data wasn't initialized.
-            // It's also possible that the sample at 0 would be invalid, but there's no harm in
-            // processing it, since it would be just a single point, and will only be encountered
-            // in artificial circumstances (in tests).
-            break;
-        }
-
-        positions[m] = movement.position;
-        time[m] = movement.eventTime;
-        index = (index == 0 ? HISTORY_SIZE : index) - 1;
-    } while (++m < HISTORY_SIZE);
-
-    if (m == 0) {
+    const RingBuffer<Movement>& movements = movementIt->second;
+    const size_t size = movements.size();
+    if (size == 0) {
         return std::nullopt; // no data
     }
-    VelocityTracker::Estimator estimator;
-    estimator.coeff[0] = 0;
-    estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues);
-    estimator.coeff[2] = 0;
 
-    estimator.time = newestMovement.eventTime;
-    estimator.degree = 2; // similar results to 2nd degree fit
-    estimator.confidence = 1;
+    float work = 0;
+    for (size_t i = 0; i < size - 1; i++) {
+        const Movement& mvt = movements[i];
+        const Movement& nextMvt = movements[i + 1];
 
-    ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]);
+        float vprev = kineticEnergyToVelocity(work);
+        float delta = mDeltaValues ? nextMvt.position : nextMvt.position - mvt.position;
+        float vcurr = delta / (SECONDS_PER_NANO * (nextMvt.eventTime - mvt.eventTime));
+        work += (vcurr - vprev) * fabsf(vcurr);
+
+        if (i == 0) {
+            work *= 0.5; // initial condition, case 2) above
+        }
+    }
+
+    const float velocity = kineticEnergyToVelocity(work);
+    ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity);
 
     if (DEBUG_IMPULSE) {
         // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
         // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
         // X axis chosen arbitrarily for velocity comparisons.
         VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
-        for (ssize_t i = m - 1; i >= 0; i--) {
-            lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]);
+        for (size_t i = 0; i < size; i++) {
+            const Movement& mvt = movements[i];
+            lsq2.addMovement(mvt.eventTime, pointerId, AMOTION_EVENT_AXIS_X, mvt.position);
         }
         std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
         if (v) {
@@ -1237,7 +1026,7 @@
             ALOGD("lsq2 velocity: could not compute velocity");
         }
     }
-    return estimator;
+    return velocity;
 }
 
 } // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index dab843b..8f6f95b 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -57,4 +57,88 @@
 
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
+
+    /**
+     * Use the default Velocity Tracker Strategy. Different axes may use different default
+     * strategies.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_DEFAULT = -1;
+
+    /**
+     * Velocity Tracker Strategy: Impulse.
+     * Physical model of pushing an object.  Quality: VERY GOOD.
+     * Works with duplicate coordinates, unclean finger liftoff.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_IMPULSE = 0;
+
+    /**
+     * Velocity Tracker Strategy: LSQ1.
+     * 1st order least squares.  Quality: POOR.
+     * Frequently underfits the touch data especially when the finger accelerates
+     * or changes direction.  Often underestimates velocity.  The direction
+     * is overly influenced by historical touch points.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_LSQ1 = 1;
+
+    /**
+     * Velocity Tracker Strategy: LSQ2.
+     * 2nd order least squares.  Quality: VERY GOOD.
+     * Pretty much ideal, but can be confused by certain kinds of touch data,
+     * particularly if the panel has a tendency to generate delayed,
+     * duplicate or jittery touch coordinates when the finger is released.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_LSQ2 = 2;
+
+    /**
+     * Velocity Tracker Strategy: LSQ3.
+     * 3rd order least squares.  Quality: UNUSABLE.
+     * Frequently overfits the touch data yielding wildly divergent estimates
+     * of the velocity when the finger is released.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_LSQ3 = 3;
+
+    /**
+     * Velocity Tracker Strategy: WLSQ2_DELTA.
+     * 2nd order weighted least squares, delta weighting.  Quality: EXPERIMENTAL
+     */
+    const int VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA = 4;
+
+    /**
+     * Velocity Tracker Strategy: WLSQ2_CENTRAL.
+     * 2nd order weighted least squares, central weighting.  Quality: EXPERIMENTALe
+     */
+    const int VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL = 5;
+
+    /**
+     * Velocity Tracker Strategy: WLSQ2_RECENT.
+     * 2nd order weighted least squares, recent weighting.  Quality: EXPERIMENTAL
+     */
+    const int VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT = 6;
+
+    /**
+     * Velocity Tracker Strategy: INT1.
+     * 1st order integrating filter.  Quality: GOOD.
+     * Not as good as 'lsq2' because it cannot estimate acceleration but it is
+     * more tolerant of errors.  Like 'lsq1', this strategy tends to underestimate
+     * the velocity of a fling but this strategy tends to respond to changes in
+     * direction more quickly and accurately.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_INT1 = 7;
+
+    /**
+     * Velocity Tracker Strategy: INT2.
+     * 2nd order integrating filter.  Quality: EXPERIMENTAL.
+     * For comparison purposes only.  Unlike 'int1' this strategy can compensate
+     * for acceleration but it typically overestimates the effect.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_INT2 = 8;
+
+    /**
+     * Velocity Tracker Strategy: Legacy.
+     * Legacy velocity tracker algorithm.  Quality: POOR.
+     * For comparison purposes only.  This algorithm is strongly influenced by
+     * old data points, consistently underestimates velocity and takes a very long
+     * time to adjust to changes in direction.
+     */
+    const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
 }
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
new file mode 100644
index 0000000..6302ff5
--- /dev/null
+++ b/libs/input/input_flags.aconfig
@@ -0,0 +1,43 @@
+package: "com.android.input.flags"
+
+flag {
+  name: "enable_outbound_event_verification"
+  namespace: "input"
+  description: "Set to true to enable crashing whenever bad outbound events are detected inside InputTransport"
+  bug: "271455682"
+}
+
+flag {
+  name: "enable_inbound_event_verification"
+  namespace: "input"
+  description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+  bug: "271455682"
+}
+
+flag {
+  name: "enable_pointer_choreographer"
+  namespace: "input"
+  description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
+  bug: "293587049"
+}
+
+flag {
+  name: "enable_gestures_library_timer_provider"
+  namespace: "input"
+  description: "Set to true to enable timer support for the touchpad Gestures library"
+  bug: "297192727"
+}
+
+flag {
+  name: "enable_multi_device_input"
+  namespace: "input"
+  description: "Set to true to enable multi-device input: touch and stylus can be active at the same time, but in different windows"
+  bug: "211379801"
+}
+
+flag {
+  name: "a11y_crash_on_inconsistent_event_stream"
+  namespace: "accessibility"
+  description: "Brings back fatal logging for inconsistent event streams originating from accessibility."
+  bug: "299977100"
+}
diff --git a/libs/input/input_verifier.rs b/libs/input/input_verifier.rs
deleted file mode 100644
index dd2ac4c..0000000
--- a/libs/input/input_verifier.rs
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//! Validate the incoming motion stream.
-//! This class is not thread-safe.
-//! State is stored in the "InputVerifier" object
-//! that can be created via the 'create' method.
-//! Usage:
-//! Box<InputVerifier> verifier = create("inputChannel name");
-//! result = process_movement(verifier, ...);
-//! if (result) {
-//!    crash(result.error_message());
-//! }
-
-use std::collections::HashMap;
-use std::collections::HashSet;
-
-use bitflags::bitflags;
-use log::info;
-
-#[cxx::bridge(namespace = "android::input")]
-#[allow(unsafe_op_in_unsafe_fn)]
-mod ffi {
-    #[namespace = "android"]
-    unsafe extern "C++" {
-        include!("ffi/FromRustToCpp.h");
-        fn shouldLog(tag: &str) -> bool;
-    }
-    #[namespace = "android::input::verifier"]
-    extern "Rust" {
-        type InputVerifier;
-
-        fn create(name: String) -> Box<InputVerifier>;
-        fn process_movement(
-            verifier: &mut InputVerifier,
-            device_id: i32,
-            action: u32,
-            pointer_properties: &[RustPointerProperties],
-            flags: i32,
-        ) -> String;
-    }
-
-    pub struct RustPointerProperties {
-        id: i32,
-    }
-}
-
-use crate::ffi::shouldLog;
-use crate::ffi::RustPointerProperties;
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-struct DeviceId(i32);
-
-fn process_movement(
-    verifier: &mut InputVerifier,
-    device_id: i32,
-    action: u32,
-    pointer_properties: &[RustPointerProperties],
-    flags: i32,
-) -> String {
-    let result = verifier.process_movement(
-        DeviceId(device_id),
-        action,
-        pointer_properties,
-        Flags::from_bits(flags).unwrap(),
-    );
-    match result {
-        Ok(()) => "".to_string(),
-        Err(e) => e,
-    }
-}
-
-fn create(name: String) -> Box<InputVerifier> {
-    Box::new(InputVerifier::new(&name))
-}
-
-#[repr(u32)]
-enum MotionAction {
-    Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-    Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
-    Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-    Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
-    Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
-    PointerDown { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
-    PointerUp { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
-    HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-    HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-    HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
-    Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
-    ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-    ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-}
-
-fn get_action_index(action: u32) -> usize {
-    let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
-        >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-    index.try_into().unwrap()
-}
-
-impl From<u32> for MotionAction {
-    fn from(action: u32) -> Self {
-        let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
-        let action_index = get_action_index(action);
-        match action_masked {
-            input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
-            input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
-            input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
-            input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
-            input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
-            input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
-                MotionAction::PointerDown { action_index }
-            }
-            input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
-                MotionAction::PointerUp { action_index }
-            }
-            input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
-            input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
-            input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
-            input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
-            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
-            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
-            _ => panic!("Unknown action: {}", action),
-        }
-    }
-}
-
-bitflags! {
-    struct Flags: i32 {
-        const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
-    }
-}
-
-fn motion_action_to_string(action: u32) -> String {
-    match action.into() {
-        MotionAction::Down => "DOWN".to_string(),
-        MotionAction::Up => "UP".to_string(),
-        MotionAction::Move => "MOVE".to_string(),
-        MotionAction::Cancel => "CANCEL".to_string(),
-        MotionAction::Outside => "OUTSIDE".to_string(),
-        MotionAction::PointerDown { action_index } => {
-            format!("POINTER_DOWN({})", action_index)
-        }
-        MotionAction::PointerUp { action_index } => {
-            format!("POINTER_UP({})", action_index)
-        }
-        MotionAction::HoverMove => "HOVER_MOVE".to_string(),
-        MotionAction::Scroll => "SCROLL".to_string(),
-        MotionAction::HoverEnter => "HOVER_ENTER".to_string(),
-        MotionAction::HoverExit => "HOVER_EXIT".to_string(),
-        MotionAction::ButtonPress => "BUTTON_PRESS".to_string(),
-        MotionAction::ButtonRelease => "BUTTON_RELEASE".to_string(),
-    }
-}
-
-/**
- * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
- * to inconsistent events.
- * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
- */
-fn log_events() -> bool {
-    shouldLog("InputVerifierLogEvents")
-}
-
-struct InputVerifier {
-    name: String,
-    touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
-}
-
-impl InputVerifier {
-    fn new(name: &str) -> Self {
-        logger::init(
-            logger::Config::default()
-                .with_tag_on_device("InputVerifier")
-                .with_min_level(log::Level::Trace),
-        );
-        Self { name: name.to_owned(), touching_pointer_ids_by_device: HashMap::new() }
-    }
-
-    fn process_movement(
-        &mut self,
-        device_id: DeviceId,
-        action: u32,
-        pointer_properties: &[RustPointerProperties],
-        flags: Flags,
-    ) -> Result<(), String> {
-        if log_events() {
-            info!(
-                "Processing {} for device {:?} ({} pointer{}) on {}",
-                motion_action_to_string(action),
-                device_id,
-                pointer_properties.len(),
-                if pointer_properties.len() == 1 { "" } else { "s" },
-                self.name
-            );
-        }
-
-        match action.into() {
-            MotionAction::Down => {
-                let it = self
-                    .touching_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
-                let pointer_id = pointer_properties[0].id;
-                if it.contains(&pointer_id) {
-                    return Err(format!(
-                        "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
-                        self.name, device_id, it
-                    ));
-                }
-                it.insert(pointer_id);
-            }
-            MotionAction::PointerDown { action_index } => {
-                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
-                    return Err(format!(
-                        "{}: Received POINTER_DOWN but no pointers are currently down \
-                        for device {:?}",
-                        self.name, device_id
-                    ));
-                }
-                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
-                let pointer_id = pointer_properties[action_index].id;
-                if it.contains(&pointer_id) {
-                    return Err(format!(
-                        "{}: Pointer with id={} not found in the properties",
-                        self.name, pointer_id
-                    ));
-                }
-                it.insert(pointer_id);
-            }
-            MotionAction::Move => {
-                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
-                    return Err(format!(
-                        "{}: ACTION_MOVE touching pointers don't match",
-                        self.name
-                    ));
-                }
-            }
-            MotionAction::PointerUp { action_index } => {
-                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
-                    return Err(format!(
-                        "{}: Received POINTER_UP but no pointers are currently down for device \
-                        {:?}",
-                        self.name, device_id
-                    ));
-                }
-                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
-                let pointer_id = pointer_properties[action_index].id;
-                it.remove(&pointer_id);
-            }
-            MotionAction::Up => {
-                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
-                    return Err(format!(
-                        "{} Received ACTION_UP but no pointers are currently down for device {:?}",
-                        self.name, device_id
-                    ));
-                }
-                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
-                if it.len() != 1 {
-                    return Err(format!(
-                        "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
-                        self.name, it, device_id
-                    ));
-                }
-                let pointer_id = pointer_properties[0].id;
-                if !it.contains(&pointer_id) {
-                    return Err(format!(
-                        "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
-                        {:?} for device {:?}",
-                        self.name, pointer_id, it, device_id
-                    ));
-                }
-                it.clear();
-            }
-            MotionAction::Cancel => {
-                if flags.contains(Flags::CANCELED) {
-                    return Err(format!(
-                        "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
-                        self.name
-                    ));
-                }
-                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
-                    return Err(format!(
-                        "{}: Got ACTION_CANCEL, but the pointers don't match. \
-                        Existing pointers: {:?}",
-                        self.name, self.touching_pointer_ids_by_device
-                    ));
-                }
-                self.touching_pointer_ids_by_device.remove(&device_id);
-            }
-            _ => return Ok(()),
-        }
-        Ok(())
-    }
-
-    fn ensure_touching_pointers_match(
-        &self,
-        device_id: DeviceId,
-        pointer_properties: &[RustPointerProperties],
-    ) -> bool {
-        let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
-            return false;
-        };
-
-        for pointer_property in pointer_properties.iter() {
-            let pointer_id = pointer_property.id;
-            if !pointers.contains(&pointer_id) {
-                return false;
-            }
-        }
-        true
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::DeviceId;
-    use crate::Flags;
-    use crate::InputVerifier;
-    use crate::RustPointerProperties;
-    #[test]
-    fn single_pointer_stream() {
-        let mut verifier = InputVerifier::new("Test");
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-    }
-
-    #[test]
-    fn multi_device_stream() {
-        let mut verifier = InputVerifier::new("Test");
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(2),
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(2),
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_ok());
-    }
-
-    #[test]
-    fn test_invalid_up() {
-        let mut verifier = InputVerifier::new("Test");
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                &pointer_properties,
-                Flags::empty(),
-            )
-            .is_err());
-    }
-}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
new file mode 100644
index 0000000..018d199
--- /dev/null
+++ b/libs/input/rust/Android.bp
@@ -0,0 +1,72 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+rust_defaults {
+    name: "libinput_rust_defaults",
+    crate_name: "input",
+    srcs: ["lib.rs"],
+    host_supported: true,
+    rustlibs: [
+        "libbitflags",
+        "libcxx",
+        "libinput_bindgen",
+        "liblogger",
+        "liblog_rust",
+        "inputconstants-rust",
+    ],
+    whole_static_libs: [
+        "libinput_from_rust_to_cpp",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+rust_library {
+    name: "libinput_rust",
+    defaults: ["libinput_rust_defaults"],
+}
+
+rust_ffi_static {
+    name: "libinput_rust_ffi",
+    defaults: ["libinput_rust_defaults"],
+}
+
+rust_test {
+    name: "libinput_rust_test",
+    defaults: ["libinput_rust_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+    test_suites: ["device_tests"],
+    sanitize: {
+        hwaddress: true,
+    },
+}
+
+genrule {
+    name: "libinput_cxx_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["input_cxx_bridge_generated.cpp"],
+}
+
+genrule {
+    name: "libinput_cxx_bridge_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["input_cxx_bridge.rs.h"],
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
new file mode 100644
index 0000000..705c959
--- /dev/null
+++ b/libs/input/rust/input.rs
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Common definitions of the Android Input Framework in rust.
+
+use bitflags::bitflags;
+use std::fmt;
+
+/// The InputDevice ID.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct DeviceId(pub i32);
+
+#[repr(u32)]
+pub enum SourceClass {
+    None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
+    Button = input_bindgen::AINPUT_SOURCE_CLASS_BUTTON,
+    Pointer = input_bindgen::AINPUT_SOURCE_CLASS_POINTER,
+    Navigation = input_bindgen::AINPUT_SOURCE_CLASS_NAVIGATION,
+    Position = input_bindgen::AINPUT_SOURCE_CLASS_POSITION,
+    Joystick = input_bindgen::AINPUT_SOURCE_CLASS_JOYSTICK,
+}
+
+bitflags! {
+    /// Source of the input device or input events.
+    #[derive(Debug, PartialEq)]
+    pub struct Source: u32 {
+        // Constants from SourceClass, added here for compatibility reasons
+        /// SourceClass::Button
+        const SourceClassButton = SourceClass::Button as u32;
+        /// SourceClass::Pointer
+        const SourceClassPointer = SourceClass::Pointer as u32;
+        /// SourceClass::Navigation
+        const SourceClassNavigation = SourceClass::Navigation as u32;
+        /// SourceClass::Position
+        const SourceClassPosition = SourceClass::Position as u32;
+        /// SourceClass::Joystick
+        const SourceClassJoystick = SourceClass::Joystick as u32;
+
+        /// SOURCE_UNKNOWN
+        const Unknown = input_bindgen::AINPUT_SOURCE_UNKNOWN;
+        /// SOURCE_KEYBOARD
+        const Keyboard = input_bindgen::AINPUT_SOURCE_KEYBOARD;
+        /// SOURCE_DPAD
+        const Dpad = input_bindgen::AINPUT_SOURCE_DPAD;
+        /// SOURCE_GAMEPAD
+        const Gamepad = input_bindgen::AINPUT_SOURCE_GAMEPAD;
+        /// SOURCE_TOUCHSCREEN
+        const Touchscreen = input_bindgen::AINPUT_SOURCE_TOUCHSCREEN;
+        /// SOURCE_MOUSE
+        const Mouse = input_bindgen::AINPUT_SOURCE_MOUSE;
+        /// SOURCE_STYLUS
+        const Stylus = input_bindgen::AINPUT_SOURCE_STYLUS;
+        /// SOURCE_BLUETOOTH_STYLUS
+        const BluetoothStylus = input_bindgen::AINPUT_SOURCE_BLUETOOTH_STYLUS;
+        /// SOURCE_TRACKBALL
+        const Trackball = input_bindgen::AINPUT_SOURCE_TRACKBALL;
+        /// SOURCE_MOUSE_RELATIVE
+        const MouseRelative = input_bindgen::AINPUT_SOURCE_MOUSE_RELATIVE;
+        /// SOURCE_TOUCHPAD
+        const Touchpad = input_bindgen::AINPUT_SOURCE_TOUCHPAD;
+        /// SOURCE_TOUCH_NAVIGATION
+        const TouchNavigation = input_bindgen::AINPUT_SOURCE_TOUCH_NAVIGATION;
+        /// SOURCE_JOYSTICK
+        const Joystick = input_bindgen::AINPUT_SOURCE_JOYSTICK;
+        /// SOURCE_HDMI
+        const HDMI = input_bindgen::AINPUT_SOURCE_HDMI;
+        /// SOURCE_SENSOR
+        const Sensor = input_bindgen::AINPUT_SOURCE_SENSOR;
+        /// SOURCE_ROTARY_ENCODER
+        const RotaryEncoder = input_bindgen::AINPUT_SOURCE_ROTARY_ENCODER;
+    }
+}
+
+/// A rust enum representation of a MotionEvent action.
+#[repr(u32)]
+pub enum MotionAction {
+    /// ACTION_DOWN
+    Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+    /// ACTION_UP
+    Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
+    /// ACTION_MOVE
+    Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+    /// ACTION_CANCEL
+    Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+    /// ACTION_OUTSIDE
+    Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
+    /// ACTION_POINTER_DOWN
+    PointerDown {
+        /// The index of the affected pointer.
+        action_index: usize,
+    } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
+    /// ACTION_POINTER_UP
+    PointerUp {
+        /// The index of the affected pointer.
+        action_index: usize,
+    } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
+    /// ACTION_HOVER_ENTER
+    HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+    /// ACTION_HOVER_MOVE
+    HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+    /// ACTION_HOVER_EXIT
+    HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+    /// ACTION_SCROLL
+    Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
+    /// ACTION_BUTTON_PRESS
+    ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+    /// ACTION_BUTTON_RELEASE
+    ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+}
+
+impl fmt::Display for MotionAction {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            MotionAction::Down => write!(f, "DOWN"),
+            MotionAction::Up => write!(f, "UP"),
+            MotionAction::Move => write!(f, "MOVE"),
+            MotionAction::Cancel => write!(f, "CANCEL"),
+            MotionAction::Outside => write!(f, "OUTSIDE"),
+            MotionAction::PointerDown { action_index } => {
+                write!(f, "POINTER_DOWN({})", action_index)
+            }
+            MotionAction::PointerUp { action_index } => write!(f, "POINTER_UP({})", action_index),
+            MotionAction::HoverMove => write!(f, "HOVER_MOVE"),
+            MotionAction::Scroll => write!(f, "SCROLL"),
+            MotionAction::HoverEnter => write!(f, "HOVER_ENTER"),
+            MotionAction::HoverExit => write!(f, "HOVER_EXIT"),
+            MotionAction::ButtonPress => write!(f, "BUTTON_PRESS"),
+            MotionAction::ButtonRelease => write!(f, "BUTTON_RELEASE"),
+        }
+    }
+}
+
+impl From<u32> for MotionAction {
+    fn from(action: u32) -> Self {
+        let (action_masked, action_index) = MotionAction::breakdown_action(action);
+        match action_masked {
+            input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
+            input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
+            input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
+            input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
+            input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
+            input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
+                MotionAction::PointerDown { action_index }
+            }
+            input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
+                MotionAction::PointerUp { action_index }
+            }
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
+            input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
+            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
+            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+            _ => panic!("Unknown action: {}", action),
+        }
+    }
+}
+
+impl MotionAction {
+    fn breakdown_action(action: u32) -> (u32, usize) {
+        let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
+        let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+            >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+        (action_masked, index.try_into().unwrap())
+    }
+}
+
+bitflags! {
+    /// MotionEvent flags.
+    #[derive(Debug)]
+    pub struct MotionFlags: u32 {
+        /// FLAG_CANCELED
+        const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
+        /// FLAG_WINDOW_IS_OBSCURED
+        const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+        const WINDOW_IS_PARTIALLY_OBSCURED =
+                input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+        /// FLAG_IS_ACCESSIBILITY_EVENT
+        const IS_ACCESSIBILITY_EVENT =
+                input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+        /// FLAG_NO_FOCUS_CHANGE
+        const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+    }
+}
+
+impl Source {
+    /// Return true if this source is from the provided class
+    pub fn is_from_class(&self, source_class: SourceClass) -> bool {
+        let class_bits = source_class as u32;
+        self.bits() & class_bits == class_bits
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input::SourceClass;
+    use crate::Source;
+    #[test]
+    fn convert_source_class_pointer() {
+        let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
+        assert!(source.is_from_class(SourceClass::Pointer));
+    }
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
new file mode 100644
index 0000000..bbc6d98
--- /dev/null
+++ b/libs/input/rust/input_verifier.rs
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Contains the InputVerifier, used to validate a stream of input events.
+
+use crate::ffi::RustPointerProperties;
+use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
+use log::info;
+use std::collections::HashMap;
+use std::collections::HashSet;
+
+fn verify_event(
+    action: MotionAction,
+    pointer_properties: &[RustPointerProperties],
+    flags: &MotionFlags,
+) -> Result<(), String> {
+    let pointer_count = pointer_properties.len();
+    if pointer_count < 1 {
+        return Err(format!("Invalid {} event: no pointers", action));
+    }
+    match action {
+        MotionAction::Down
+        | MotionAction::HoverEnter
+        | MotionAction::HoverExit
+        | MotionAction::HoverMove
+        | MotionAction::Up => {
+            if pointer_count != 1 {
+                return Err(format!(
+                    "Invalid {} event: there are {} pointers in the event",
+                    action, pointer_count
+                ));
+            }
+        }
+
+        MotionAction::Cancel => {
+            if !flags.contains(MotionFlags::CANCELED) {
+                return Err(format!(
+                    "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
+                    flags
+                ));
+            }
+        }
+
+        MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
+            if action_index >= pointer_count {
+                return Err(format!("Got {}, but event has {} pointer(s)", action, pointer_count));
+            }
+        }
+
+        _ => {}
+    }
+    Ok(())
+}
+
+/// The InputVerifier is used to validate a stream of input events.
+pub struct InputVerifier {
+    name: String,
+    should_log: bool,
+    touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+    hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+}
+
+impl InputVerifier {
+    /// Create a new InputVerifier.
+    pub fn new(name: &str, should_log: bool) -> Self {
+        logger::init(
+            logger::Config::default()
+                .with_tag_on_device("InputVerifier")
+                .with_min_level(log::Level::Trace),
+        );
+        Self {
+            name: name.to_owned(),
+            should_log,
+            touching_pointer_ids_by_device: HashMap::new(),
+            hovering_pointer_ids_by_device: HashMap::new(),
+        }
+    }
+
+    /// Process a pointer movement event from an InputDevice.
+    /// If the event is not valid, we return an error string that describes the issue.
+    pub fn process_movement(
+        &mut self,
+        device_id: DeviceId,
+        source: Source,
+        action: u32,
+        pointer_properties: &[RustPointerProperties],
+        flags: MotionFlags,
+    ) -> Result<(), String> {
+        if !source.is_from_class(SourceClass::Pointer) {
+            // Skip non-pointer sources like MOUSE_RELATIVE for now
+            return Ok(());
+        }
+        if self.should_log {
+            info!(
+                "Processing {} for device {:?} ({} pointer{}) on {}",
+                MotionAction::from(action).to_string(),
+                device_id,
+                pointer_properties.len(),
+                if pointer_properties.len() == 1 { "" } else { "s" },
+                self.name
+            );
+        }
+
+        verify_event(action.into(), pointer_properties, &flags)?;
+
+        match action.into() {
+            MotionAction::Down => {
+                let it = self
+                    .touching_pointer_ids_by_device
+                    .entry(device_id)
+                    .or_insert_with(HashSet::new);
+                let pointer_id = pointer_properties[0].id;
+                if it.contains(&pointer_id) {
+                    return Err(format!(
+                        "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
+                        self.name, device_id, it
+                    ));
+                }
+                it.insert(pointer_id);
+            }
+            MotionAction::PointerDown { action_index } => {
+                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{}: Received POINTER_DOWN but no pointers are currently down \
+                        for device {:?}",
+                        self.name, device_id
+                    ));
+                }
+                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                if it.len() != pointer_properties.len() - 1 {
+                    return Err(format!(
+                        "{}: There are currently {} touching pointers, but the incoming \
+                         POINTER_DOWN event has {}",
+                        self.name,
+                        it.len(),
+                        pointer_properties.len()
+                    ));
+                }
+                let pointer_id = pointer_properties[action_index].id;
+                if it.contains(&pointer_id) {
+                    return Err(format!(
+                        "{}: Pointer with id={} already present found in the properties",
+                        self.name, pointer_id
+                    ));
+                }
+                it.insert(pointer_id);
+            }
+            MotionAction::Move => {
+                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                    return Err(format!(
+                        "{}: ACTION_MOVE touching pointers don't match",
+                        self.name
+                    ));
+                }
+            }
+            MotionAction::PointerUp { action_index } => {
+                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                    return Err(format!(
+                        "{}: ACTION_POINTER_UP touching pointers don't match",
+                        self.name
+                    ));
+                }
+                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                let pointer_id = pointer_properties[action_index].id;
+                it.remove(&pointer_id);
+            }
+            MotionAction::Up => {
+                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{} Received ACTION_UP but no pointers are currently down for device {:?}",
+                        self.name, device_id
+                    ));
+                }
+                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                if it.len() != 1 {
+                    return Err(format!(
+                        "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
+                        self.name, it, device_id
+                    ));
+                }
+                let pointer_id = pointer_properties[0].id;
+                if !it.contains(&pointer_id) {
+                    return Err(format!(
+                        "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
+                        {:?} for device {:?}",
+                        self.name, pointer_id, it, device_id
+                    ));
+                }
+                self.touching_pointer_ids_by_device.remove(&device_id);
+            }
+            MotionAction::Cancel => {
+                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                    return Err(format!(
+                        "{}: Got ACTION_CANCEL, but the pointers don't match. \
+                        Existing pointers: {:?}",
+                        self.name, self.touching_pointer_ids_by_device
+                    ));
+                }
+                self.touching_pointer_ids_by_device.remove(&device_id);
+            }
+            /*
+             * The hovering protocol currently supports a single pointer only, because we do not
+             * have ACTION_HOVER_POINTER_ENTER or ACTION_HOVER_POINTER_EXIT.
+             * Still, we are keeping the infrastructure here pretty general in case that is
+             * eventually supported.
+             */
+            MotionAction::HoverEnter => {
+                if self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\
+                        {:?}",
+                        self.name, device_id, self.hovering_pointer_ids_by_device
+                    ));
+                }
+                let it = self
+                    .hovering_pointer_ids_by_device
+                    .entry(device_id)
+                    .or_insert_with(HashSet::new);
+                it.insert(pointer_properties[0].id);
+            }
+            MotionAction::HoverMove => {
+                // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
+                // If there was no prior HOVER_ENTER, just start a new hovering pointer.
+                let it = self
+                    .hovering_pointer_ids_by_device
+                    .entry(device_id)
+                    .or_insert_with(HashSet::new);
+                it.insert(pointer_properties[0].id);
+            }
+            MotionAction::HoverExit => {
+                if !self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}",
+                        self.name, device_id
+                    ));
+                }
+                let pointer_id = pointer_properties[0].id;
+                let it = self.hovering_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                it.remove(&pointer_id);
+
+                if !it.is_empty() {
+                    return Err(format!(
+                        "{}: Removed hovering pointer {}, but pointers are still\
+                               hovering for device {:?}: {:?}",
+                        self.name, pointer_id, device_id, it
+                    ));
+                }
+                self.hovering_pointer_ids_by_device.remove(&device_id);
+            }
+            _ => return Ok(()),
+        }
+        Ok(())
+    }
+
+    /// Notify the verifier that the device has been reset, which will cause the verifier to erase
+    /// the current internal state for this device. Subsequent events from this device are expected
+    //// to start a new gesture.
+    pub fn reset_device(&mut self, device_id: DeviceId) {
+        self.touching_pointer_ids_by_device.remove(&device_id);
+        self.hovering_pointer_ids_by_device.remove(&device_id);
+    }
+
+    fn ensure_touching_pointers_match(
+        &self,
+        device_id: DeviceId,
+        pointer_properties: &[RustPointerProperties],
+    ) -> bool {
+        let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
+            return false;
+        };
+
+        for pointer_property in pointer_properties.iter() {
+            let pointer_id = pointer_property.id;
+            if !pointers.contains(&pointer_id) {
+                return false;
+            }
+        }
+        true
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_verifier::InputVerifier;
+    use crate::DeviceId;
+    use crate::MotionFlags;
+    use crate::RustPointerProperties;
+    use crate::Source;
+
+    #[test]
+    /**
+     * Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
+     */
+    fn bad_down_event() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ true);
+        let pointer_properties =
+            Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn single_pointer_stream() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn multi_device_stream() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(2),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(2),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn action_cancel() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+                &pointer_properties,
+                MotionFlags::CANCELED,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn invalid_action_cancel() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+                &pointer_properties,
+                MotionFlags::empty(), // forgot to set FLAG_CANCELED
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn invalid_up() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn correct_hover_sequence() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn double_hover_enter() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_err());
+    }
+
+    // Send a MOVE without a preceding DOWN event. This is OK because it's from source
+    // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
+    #[test]
+    fn relative_mouse_move() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(2),
+                Source::MouseRelative,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+    }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
new file mode 100644
index 0000000..01d9599
--- /dev/null
+++ b/libs/input/rust/lib.rs
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! The rust component of libinput.
+
+mod input;
+mod input_verifier;
+
+pub use input::{DeviceId, MotionAction, MotionFlags, Source};
+pub use input_verifier::InputVerifier;
+
+#[cxx::bridge(namespace = "android::input")]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod ffi {
+    #[namespace = "android"]
+    unsafe extern "C++" {
+        include!("ffi/FromRustToCpp.h");
+        fn shouldLog(tag: &str) -> bool;
+    }
+
+    #[namespace = "android::input::verifier"]
+    extern "Rust" {
+        /// Used to validate the incoming motion stream.
+        /// This class is not thread-safe.
+        /// State is stored in the "InputVerifier" object
+        /// that can be created via the 'create' method.
+        /// Usage:
+        ///
+        /// ```ignore
+        /// Box<InputVerifier> verifier = create("inputChannel name");
+        /// result = process_movement(verifier, ...);
+        /// if (result) {
+        ///    crash(result.error_message());
+        /// }
+        /// ```
+        type InputVerifier;
+        fn create(name: String) -> Box<InputVerifier>;
+        fn process_movement(
+            verifier: &mut InputVerifier,
+            device_id: i32,
+            source: u32,
+            action: u32,
+            pointer_properties: &[RustPointerProperties],
+            flags: u32,
+        ) -> String;
+        fn reset_device(verifier: &mut InputVerifier, device_id: i32);
+    }
+
+    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+    pub struct RustPointerProperties {
+        pub id: i32,
+    }
+}
+
+use crate::ffi::RustPointerProperties;
+
+fn create(name: String) -> Box<InputVerifier> {
+    Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+}
+
+fn process_movement(
+    verifier: &mut InputVerifier,
+    device_id: i32,
+    source: u32,
+    action: u32,
+    pointer_properties: &[RustPointerProperties],
+    flags: u32,
+) -> String {
+    let result = verifier.process_movement(
+        DeviceId(device_id),
+        Source::from_bits(source).unwrap(),
+        action,
+        pointer_properties,
+        MotionFlags::from_bits(flags).unwrap(),
+    );
+    match result {
+        Ok(()) => "".to_string(),
+        Err(e) => e,
+    }
+}
+
+fn reset_device(verifier: &mut InputVerifier, device_id: i32) {
+    verifier.reset_device(DeviceId(device_id));
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e7224ff..138898f 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -62,6 +62,7 @@
         "libtinyxml2",
         "libutils",
         "libvintf",
+        "server_configurable_flags",
     ],
     data: [
         "data/*",
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index ee961f0..fe5490c 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -20,6 +20,7 @@
 #include <input/InputDevice.h>
 #include <input/KeyLayoutMap.h>
 #include <input/Keyboard.h>
+#include <linux/uinput.h>
 #include "android-base/file.h"
 
 namespace android {
@@ -97,7 +98,7 @@
     ASSERT_EQ(*map, *mKeyMap.keyCharacterMap);
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyMultipleOverlaysTest) {
     std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
     std::string englishOverlayPath = base::GetExecutableDirectory() + "/data/english_us.kcm";
     std::string germanOverlayPath = base::GetExecutableDirectory() + "/data/german.kcm";
@@ -133,20 +134,74 @@
     ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyOverlayTest) {
+    std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
+    base::Result<std::shared_ptr<KeyCharacterMap>> frenchOverlay =
+            KeyCharacterMap::load(frenchOverlayPath, KeyCharacterMap::Format::OVERLAY);
+    ASSERT_TRUE(frenchOverlay.ok()) << "Cannot load KeyCharacterMap at " << frenchOverlayPath;
+
+    // Apply the French overlay
+    mKeyMap.keyCharacterMap->combine(*frenchOverlay->get());
+
+    // Check if mapping for key_Q is correct
+    int32_t outKeyCode;
+    status_t mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_Q, /*usageCode=*/0, &outKeyCode);
+    ASSERT_EQ(mapKeyResult, OK) << "No mapping for KEY_Q for " << frenchOverlayPath;
+    ASSERT_EQ(outKeyCode, AKEYCODE_A);
+
+    mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_E, /*usageCode=*/0, &outKeyCode);
+    ASSERT_NE(mapKeyResult, OK) << "Mapping exists for KEY_E for " << frenchOverlayPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadAxisLabel) {
     std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl";
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
     ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadLedLabel) {
     std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl";
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
     ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
 }
 
+TEST(InputDeviceKeyLayoutTest, HidUsageCodesFallbackMapping) {
+    std::string klPath = base::GetExecutableDirectory() + "/data/hid_fallback_mapping.kl";
+    base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+    ASSERT_TRUE(ret.ok()) << "Unable to load KeyLayout at " << klPath;
+    const std::shared_ptr<KeyLayoutMap>& keyLayoutMap = *ret;
+
+    static constexpr std::array<int32_t, 5> hidUsageCodesWithoutFallback = {0x0c0067, 0x0c0070,
+                                                                            0x0c006F, 0x0c0079,
+                                                                            0x0c007A};
+    for (int32_t hidUsageCode : hidUsageCodesWithoutFallback) {
+        int32_t outKeyCode;
+        uint32_t outFlags;
+        keyLayoutMap->mapKey(0, hidUsageCode, &outKeyCode, &outFlags);
+        ASSERT_FALSE(outFlags & POLICY_FLAG_FALLBACK_USAGE_MAPPING)
+                << "HID usage code should not be marked as fallback";
+        std::vector<int32_t> usageCodes = keyLayoutMap->findUsageCodesForKey(outKeyCode);
+        ASSERT_NE(std::find(usageCodes.begin(), usageCodes.end(), hidUsageCode), usageCodes.end())
+                << "Fallback usage code should be mapped to key";
+    }
+
+    static constexpr std::array<int32_t, 6> hidUsageCodesWithFallback = {0x0c007C, 0x0c0173,
+                                                                         0x0c019C, 0x0c01A2,
+                                                                         0x0d0044, 0x0d005a};
+    for (int32_t hidUsageCode : hidUsageCodesWithFallback) {
+        int32_t outKeyCode;
+        uint32_t outFlags;
+        keyLayoutMap->mapKey(0, hidUsageCode, &outKeyCode, &outFlags);
+        ASSERT_TRUE(outFlags & POLICY_FLAG_FALLBACK_USAGE_MAPPING)
+                << "HID usage code should be marked as fallback";
+        std::vector<int32_t> usageCodes = keyLayoutMap->findUsageCodesForKey(outKeyCode);
+        ASSERT_EQ(std::find(usageCodes.begin(), usageCodes.end(), hidUsageCode), usageCodes.end())
+                << "Fallback usage code should not be mapped to key";
+    }
+}
+
 TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) {
 #if !defined(__ANDROID__)
     GTEST_SKIP() << "Can't check kernel configs on host";
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 3ecf8ee..06b841b 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -25,7 +25,22 @@
 
 namespace android {
 
-constexpr static float EPSILON = MotionEvent::ROUNDING_PRECISION;
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+struct Pointer {
+    int32_t id;
+    float x;
+    float y;
+    bool isResampled = false;
+};
+
+} // namespace
 
 class InputPublisherAndConsumerTest : public testing::Test {
 protected:
@@ -46,12 +61,28 @@
         mConsumer = std::make_unique<InputConsumer>(mClientChannel);
     }
 
-    void PublishAndConsumeKeyEvent();
-    void PublishAndConsumeMotionEvent();
-    void PublishAndConsumeFocusEvent();
-    void PublishAndConsumeCaptureEvent();
-    void PublishAndConsumeDragEvent();
-    void PublishAndConsumeTouchModeEvent();
+    void publishAndConsumeKeyEvent();
+    void publishAndConsumeMotionStream();
+    void publishAndConsumeFocusEvent();
+    void publishAndConsumeCaptureEvent();
+    void publishAndConsumeDragEvent();
+    void publishAndConsumeTouchModeEvent();
+    void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+                                      const std::vector<Pointer>& pointers);
+
+private:
+    // The sequence number to use when publishing the next event
+    uint32_t mSeq = 1;
+
+    void publishAndConsumeMotionEvent(
+            int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+            int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+            int32_t metaState, int32_t buttonState, MotionClassification classification,
+            float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
+            float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
+            float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
+            nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
+            const std::vector<PointerCoords>& pointerCoords);
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -63,10 +94,10 @@
               mConsumer->getChannel()->getConnectionToken());
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() {
     status_t status;
 
-    constexpr uint32_t seq = 15;
+    const uint32_t seq = mSeq++;
     int32_t eventId = InputEvent::nextId();
     constexpr int32_t deviceId = 1;
     constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
@@ -132,20 +163,43 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
-    status_t status;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionStream() {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    constexpr uint32_t seq = 15;
-    int32_t eventId = InputEvent::nextId();
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
     constexpr int32_t deviceId = 1;
     constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
     constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
     constexpr std::array<uint8_t, 32> hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
                                               11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
                                               22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
-    constexpr int32_t action = AMOTION_EVENT_ACTION_MOVE;
     constexpr int32_t actionButton = 0;
-    constexpr int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    const size_t pointerCount = pointers.size();
     constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
     constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
     constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
@@ -162,20 +216,21 @@
     constexpr float yPrecision = 0.5;
     constexpr float xCursorPosition = 1.3;
     constexpr float yCursorPosition = 50.6;
-    constexpr nsecs_t downTime = 3;
-    constexpr size_t pointerCount = 3;
-    constexpr nsecs_t eventTime = 4;
-    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    PointerProperties pointerProperties[pointerCount];
-    PointerCoords pointerCoords[pointerCount];
+
+    const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
     for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
         pointerProperties[i].clear();
-        pointerProperties[i].id = (i + 2) % pointerCount;
+        pointerProperties[i].id = pointers[i].id;
         pointerProperties[i].toolType = ToolType::FINGER;
 
+        pointerCoords.push_back({});
         pointerCoords[i].clear();
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i);
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
@@ -185,18 +240,40 @@
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
     }
 
+    publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
+                                 edgeFlags, metaState, buttonState, classification, xScale, yScale,
+                                 xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
+                                 yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
+                                 downTime, eventTime, pointerProperties, pointerCoords);
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+        int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+        int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+        int32_t buttonState, MotionClassification classification, float xScale, float yScale,
+        float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
+        float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
+        nsecs_t downTime, nsecs_t eventTime,
+        const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerCoords>& pointerCoords) {
+    const uint32_t seq = mSeq++;
+    const int32_t eventId = InputEvent::nextId();
     ui::Transform transform;
     transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
     ui::Transform rawTransform;
     rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+
+    status_t status;
+    ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
+    const size_t pointerCount = pointerProperties.size();
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
     status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
                                             actionButton, flags, edgeFlags, metaState, buttonState,
                                             classification, transform, xPrecision, yPrecision,
                                             xCursorPosition, yCursorPosition, rawTransform,
-                                            downTime, eventTime, pointerCount, pointerProperties,
-                                            pointerCoords);
-    ASSERT_EQ(OK, status)
-            << "publisher publishMotionEvent should return OK";
+                                            downTime, eventTime, pointerCount,
+                                            pointerProperties.data(), pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
 
     uint32_t consumeSeq;
     InputEvent* event;
@@ -280,7 +357,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
     status_t status;
 
     constexpr uint32_t seq = 15;
@@ -321,7 +398,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeCaptureEvent() {
     status_t status;
 
     constexpr uint32_t seq = 42;
@@ -361,7 +438,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeDragEvent() {
     status_t status;
 
     constexpr uint32_t seq = 15;
@@ -405,7 +482,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeTouchModeEvent() {
     status_t status;
 
     constexpr uint32_t seq = 15;
@@ -462,27 +539,27 @@
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishCaptureEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishDragEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
@@ -546,17 +623,29 @@
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
 }
 
 } // namespace android
diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp
index e24fa6e..e2eb080 100644
--- a/libs/input/tests/InputVerifier_test.cpp
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -20,10 +20,35 @@
 
 namespace android {
 
+using android::base::Result;
+
 TEST(InputVerifierTest, CreationWithInvalidUtfStringDoesNotCrash) {
     constexpr char bytes[] = {static_cast<char>(0xC0), static_cast<char>(0x80)};
     const std::string name(bytes, sizeof(bytes));
     InputVerifier verifier(name);
 }
 
+TEST(InputVerifierTest, ProcessSourceClassPointer) {
+    InputVerifier verifier("Verify testOnTouchEventScroll");
+
+    std::vector<PointerProperties> properties;
+    properties.push_back({});
+    properties.back().clear();
+    properties.back().id = 0;
+    properties.back().toolType = ToolType::UNKNOWN;
+
+    std::vector<PointerCoords> coords;
+    coords.push_back({});
+    coords.back().clear();
+    coords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 75);
+    coords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 300);
+
+    const Result<void> result =
+            verifier.processMovement(/*deviceId=*/0, AINPUT_SOURCE_CLASS_POINTER,
+                                     AMOTION_EVENT_ACTION_DOWN,
+                                     /*pointerCount=*/properties.size(), properties.data(),
+                                     coords.data(), /*flags=*/0);
+    ASSERT_TRUE(result.ok());
+}
+
 } // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 655de80..1cb7f7b 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -27,10 +27,13 @@
 
 namespace android {
 
+namespace {
+
 struct Pointer {
     int32_t id;
     float x;
     float y;
+    ToolType toolType = ToolType::FINGER;
     bool isResampled = false;
 };
 
@@ -40,6 +43,8 @@
     int32_t action;
 };
 
+} // namespace
+
 class TouchResamplingTest : public testing::Test {
 protected:
     std::unique_ptr<InputPublisher> mPublisher;
@@ -99,7 +104,7 @@
         properties.push_back({});
         properties.back().clear();
         properties.back().id = pointer.id;
-        properties.back().toolType = ToolType::FINGER;
+        properties.back().toolType = pointer.toolType;
 
         coords.push_back({});
         coords.back().clear();
@@ -292,6 +297,48 @@
 }
 
 /**
+ * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
+ * a resampled timestamp and should be marked as such.
+ */
+TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+            // A resampled event is generated, but the stylus coordinates are not resampled.
+            {25ms,
+             {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
  * Event should not be resampled when sample time is equal to event time.
  */
 TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) {
@@ -544,13 +591,13 @@
     // First pointer id=0 leaves the screen
     entries = {
             //      id  x    y
-            {80ms, {{1, 600, 600}}, actionPointer0Up},
+            {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
     };
     publishInputEventEntries(entries);
     frameTime = 90ms;
     expectedEntries = {
             //      id  x    y
-            {80ms, {{1, 600, 600}}, actionPointer0Up},
+            {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
             // no resampled event for ACTION_POINTER_UP
     };
     consumeInputEventEntries(expectedEntries, frameTime);
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 73f25cc..1c8ec90 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -42,8 +42,8 @@
 // here EV = expected value, tol = VELOCITY_TOLERANCE
 constexpr float VELOCITY_TOLERANCE = 0.2;
 
-// estimate coefficients must be within 0.001% of the target value
-constexpr float COEFFICIENT_TOLERANCE = 0.00001;
+// quadratic velocity must be within 0.001% of the target value
+constexpr float QUADRATIC_VELOCITY_TOLERANCE = 0.00001;
 
 // --- VelocityTrackerTest ---
 class VelocityTrackerTest : public testing::Test { };
@@ -76,10 +76,6 @@
     }
 }
 
-static void checkCoefficient(float actual, float target) {
-    EXPECT_NEAR_BY_FRACTION(actual, target, COEFFICIENT_TOLERANCE);
-}
-
 struct Position {
     float x;
     float y;
@@ -289,21 +285,20 @@
                   targetVelocity);
 }
 
-static void computeAndCheckQuadraticEstimate(const std::vector<PlanarMotionEventEntry>& motions,
-                                             const std::array<float, 3>& coefficients) {
+static void computeAndCheckQuadraticVelocity(const std::vector<PlanarMotionEventEntry>& motions,
+                                             float velocity) {
     VelocityTracker vt(VelocityTracker::Strategy::LSQ2);
     std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
     for (MotionEvent event : events) {
         vt.addMovement(&event);
     }
-    std::optional<VelocityTracker::Estimator> estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0);
-    std::optional<VelocityTracker::Estimator> estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0);
-    EXPECT_TRUE(estimatorX);
-    EXPECT_TRUE(estimatorY);
-    for (size_t i = 0; i< coefficients.size(); i++) {
-        checkCoefficient((*estimatorX).coeff[i], coefficients[i]);
-        checkCoefficient((*estimatorY).coeff[i], coefficients[i]);
-    }
+    std::optional<float> velocityX = vt.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+    std::optional<float> velocityY = vt.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
+    ASSERT_TRUE(velocityX);
+    ASSERT_TRUE(velocityY);
+
+    EXPECT_NEAR_BY_FRACTION(*velocityX, velocity, QUADRATIC_VELOCITY_TOLERANCE);
+    EXPECT_NEAR_BY_FRACTION(*velocityY, velocity, QUADRATIC_VELOCITY_TOLERANCE);
 }
 
 /*
@@ -466,8 +461,6 @@
 
     EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
 
-    EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
-
     VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000);
     for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) {
         EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id));
@@ -1079,7 +1072,7 @@
  * If the events with POINTER_UP or POINTER_DOWN are not handled correctly (these should not be
  * part of the fitted data), this can cause large velocity values to be reported instead.
  */
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFingerTap) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_ThreeFingerTap) {
     std::vector<PlanarMotionEventEntry> motions = {
         { 0us,      {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} },
         { 10800us,  {{1063, 1128}, {682, 1318}, {NAN, NAN}} }, // POINTER_DOWN
@@ -1167,7 +1160,7 @@
  * ================== Tests for least squares fitting ==============================================
  *
  * Special care must be taken when constructing tests for LeastSquaresVelocityTrackerStrategy
- * getEstimator function. In particular:
+ * getVelocity function. In particular:
  * - inside the function, time gets converted from nanoseconds to seconds
  *   before being used in the fit.
  * - any values that are older than 100 ms are being discarded.
@@ -1188,7 +1181,7 @@
  * The coefficients are (0, 0, 1).
  * In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2).
  */
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Constant) {
     std::vector<PlanarMotionEventEntry> motions = {
         { 0ms, {{1, 1}} }, // 0 s
         { 1ms, {{1, 1}} }, // 0.001 s
@@ -1200,13 +1193,13 @@
     // -0.002, 1
     // -0.001, 1
     // -0.ms, 1
-    computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({1, 0, 0}));
+    computeAndCheckQuadraticVelocity(motions, 0);
 }
 
 /*
  * Straight line y = x :: the constant and quadratic coefficients are zero.
  */
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Linear) {
     std::vector<PlanarMotionEventEntry> motions = {
         { 0ms, {{-2, -2}} },
         { 1ms, {{-1, -1}} },
@@ -1218,13 +1211,13 @@
     // -0.002, -2
     // -0.001, -1
     // -0.000,  0
-    computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 1E3, 0}));
+    computeAndCheckQuadraticVelocity(motions, 1E3);
 }
 
 /*
  * Parabola
  */
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic) {
     std::vector<PlanarMotionEventEntry> motions = {
         { 0ms, {{1, 1}} },
         { 1ms, {{4, 4}} },
@@ -1236,13 +1229,13 @@
     // -0.002, 1
     // -0.001, 4
     // -0.000, 8
-    computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({8, 4.5E3, 0.5E6}));
+    computeAndCheckQuadraticVelocity(motions, 4.5E3);
 }
 
 /*
  * Parabola
  */
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic2) {
     std::vector<PlanarMotionEventEntry> motions = {
         { 0ms, {{1, 1}} },
         { 1ms, {{4, 4}} },
@@ -1254,13 +1247,13 @@
     // -0.002, 1
     // -0.001, 4
     // -0.000, 9
-    computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({9, 6E3, 1E6}));
+    computeAndCheckQuadraticVelocity(motions, 6E3);
 }
 
 /*
  * Parabola :: y = x^2 :: the constant and linear coefficients are zero.
  */
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic3) {
     std::vector<PlanarMotionEventEntry> motions = {
         { 0ms, {{4, 4}} },
         { 1ms, {{1, 1}} },
@@ -1272,7 +1265,7 @@
     // -0.002, 4
     // -0.001, 1
     // -0.000, 0
-    computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 0E3, 1E6}));
+    computeAndCheckQuadraticVelocity(motions, 0E3);
 }
 
 // Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity.
diff --git a/libs/input/tests/data/hid_fallback_mapping.kl b/libs/input/tests/data/hid_fallback_mapping.kl
new file mode 100644
index 0000000..b4ca9ef
--- /dev/null
+++ b/libs/input/tests/data/hid_fallback_mapping.kl
@@ -0,0 +1,32 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# test key layout file for InputDeviceKeyMapTest#HidUsageCodesUseFallbackMapping
+#
+
+# Keys defined by HID usages without fallback mapping flag
+key usage 0x0c0067 WINDOW
+key usage 0x0c006F BRIGHTNESS_UP
+key usage 0x0c0070 BRIGHTNESS_DOWN
+key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP
+key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN
+
+# Keys defined by HID usages with fallback mapping flag
+key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c0173 MEDIA_AUDIO_TRACK         FALLBACK_USAGE_MAPPING
+key usage 0x0c019C PROFILE_SWITCH            FALLBACK_USAGE_MAPPING
+key usage 0x0c01A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
+key usage 0x0d0044 STYLUS_BUTTON_PRIMARY     FALLBACK_USAGE_MAPPING
+key usage 0x0d005a STYLUS_BUTTON_SECONDARY   FALLBACK_USAGE_MAPPING
\ No newline at end of file
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 8d8a2bc..342f5de 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -73,6 +73,8 @@
         "libGLESv2",
     ],
 
+    static_libs: ["libguiflags"],
+
     export_header_lib_headers: ["jni_headers"],
 
     header_libs: [
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 0f119f3..32fb350 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -19,6 +19,7 @@
 #include <android/hardware_buffer.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/ConsumerBase.h>
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <sys/cdefs.h>
 #include <system/graphics.h>
@@ -290,6 +291,20 @@
      */
     void releaseConsumerOwnership();
 
+    /**
+     * Interface for SurfaceTexture callback(s).
+     */
+    struct SurfaceTextureListener : public RefBase {
+        virtual void onFrameAvailable(const BufferItem& item) = 0;
+        virtual void onSetFrameRate(float frameRate, int8_t compatibility,
+                                    int8_t changeFrameRateStrategy) = 0;
+    };
+
+    /**
+     * setSurfaceTextureListener registers a SurfaceTextureListener.
+     */
+    void setSurfaceTextureListener(const sp<SurfaceTextureListener>&);
+
 protected:
     /**
      * abandonLocked overrides the ConsumerBase method to clear
@@ -335,6 +350,14 @@
     void computeCurrentTransformMatrixLocked();
 
     /**
+     * onSetFrameRate Notifies the consumer of a setFrameRate call from the producer side.
+     */
+#if FLAG_BQ_SET_FRAME_RATE
+    void onSetFrameRate(float frameRate, int8_t compatibility,
+                        int8_t changeFrameRateStrategy) override;
+#endif
+
+    /**
      * The default consumer usage flags that SurfaceTexture always sets on its
      * BufferQueue instance; these will be OR:d with any additional flags passed
      * from the SurfaceTexture user. In particular, SurfaceTexture will always
@@ -465,8 +488,30 @@
      */
     ImageConsumer mImageConsumer;
 
+    /**
+     * mSurfaceTextureListener holds the registered SurfaceTextureListener.
+     * Note that SurfaceTexture holds the lister with an sp<>, which means that the listener
+     * must only hold a wp<> to SurfaceTexture and not an sp<>.
+     */
+    sp<SurfaceTextureListener> mSurfaceTextureListener;
+
     friend class ImageConsumer;
     friend class EGLConsumer;
+
+private:
+    // Proxy listener to avoid having SurfaceTexture directly implement FrameAvailableListener as it
+    // is extending ConsumerBase which also implements FrameAvailableListener.
+    class FrameAvailableListenerProxy : public ConsumerBase::FrameAvailableListener {
+    public:
+        FrameAvailableListenerProxy(const wp<SurfaceTextureListener>& listener)
+              : mSurfaceTextureListener(listener) {}
+
+    private:
+        void onFrameAvailable(const BufferItem& item) override;
+
+        const wp<SurfaceTextureListener> mSurfaceTextureListener;
+    };
+    sp<FrameAvailableListenerProxy> mFrameAvailableListenerProxy;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index 9f610e1..c2535e0 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -23,6 +23,8 @@
 #include <system/window.h>
 #include <utils/Trace.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
 // Macros for including the SurfaceTexture name in log messages
@@ -491,4 +493,42 @@
     return buffer;
 }
 
+void SurfaceTexture::setSurfaceTextureListener(
+        const sp<android::SurfaceTexture::SurfaceTextureListener>& listener) {
+    SFT_LOGV("setSurfaceTextureListener");
+
+    Mutex::Autolock _l(mMutex);
+    mSurfaceTextureListener = listener;
+    if (mSurfaceTextureListener != nullptr) {
+        mFrameAvailableListenerProxy =
+                sp<FrameAvailableListenerProxy>::make(mSurfaceTextureListener);
+        setFrameAvailableListener(mFrameAvailableListenerProxy);
+    } else {
+        mFrameAvailableListenerProxy.clear();
+    }
+}
+
+void SurfaceTexture::FrameAvailableListenerProxy::onFrameAvailable(const BufferItem& item) {
+    const auto listener = mSurfaceTextureListener.promote();
+    if (listener) {
+        listener->onFrameAvailable(item);
+    }
+}
+
+#if FLAG_BQ_SET_FRAME_RATE
+void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
+                                    int8_t changeFrameRateStrategy) {
+    SFT_LOGV("onSetFrameRate: %.2f", frameRate);
+
+    auto listener = [&] {
+        Mutex::Autolock _l(mMutex);
+        return mSurfaceTextureListener;
+    }();
+
+    if (listener) {
+        listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+}
+#endif
+
 } // namespace android
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index 8060705..e7b2195 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -40,6 +40,80 @@
 using namespace android;
 
 // ----------------------------------------------------------------------------
+// Validate hardware_buffer.h and PixelFormat.aidl agree
+// ----------------------------------------------------------------------------
+
+static_assert(HAL_PIXEL_FORMAT_RGBA_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBX_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGB_565 == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGB_888 == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBA_FP16 == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBA_1010102 == AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_BLOB == AHARDWAREBUFFER_FORMAT_BLOB,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_16 == AHARDWAREBUFFER_FORMAT_D16_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_24 == AHARDWAREBUFFER_FORMAT_D24_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_24_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_32F == AHARDWAREBUFFER_FORMAT_D32_FLOAT,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_32F_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_STENCIL_8 == AHARDWAREBUFFER_FORMAT_S8_UINT,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_BGRA_8888 == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YV12 == AHARDWAREBUFFER_FORMAT_YV12,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_Y8 == AHARDWAREBUFFER_FORMAT_Y8,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_Y16 == AHARDWAREBUFFER_FORMAT_Y16,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW16 == AHARDWAREBUFFER_FORMAT_RAW16,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW10 == AHARDWAREBUFFER_FORMAT_RAW10,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW12 == AHARDWAREBUFFER_FORMAT_RAW12,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW_OPAQUE == AHARDWAREBUFFER_FORMAT_RAW_OPAQUE,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ==
+                      AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_420_888 == AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_422_SP == AHARDWAREBUFFER_FORMAT_YCbCr_422_SP,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCRCB_420_SP == AHARDWAREBUFFER_FORMAT_YCrCb_420_SP,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_422_I == AHARDWAREBUFFER_FORMAT_YCbCr_422_I,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
+                      AHARDWAREBUFFER_FORMAT_R8_UNORM,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
+                      AHARDWAREBUFFER_FORMAT_R16_UINT,
+              "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+        static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
+                AHARDWAREBUFFER_FORMAT_R16G16_UINT,
+        "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+        static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
+                AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
+        "HAL and AHardwareBuffer pixel format don't match");
+
+// ----------------------------------------------------------------------------
 // Public functions
 // ----------------------------------------------------------------------------
 
@@ -227,11 +301,14 @@
       }
       return result;
     } else {
-      const uint32_t pixelStride = AHardwareBuffer_bytesPerPixel(format);
+      int32_t bytesPerPixel;
+      int32_t bytesPerStride;
+      int result = gBuffer->lockAsync(usage, usage, bounds, &outPlanes->planes[0].data, fence,
+                                      &bytesPerPixel, &bytesPerStride);
       outPlanes->planeCount = 1;
-      outPlanes->planes[0].pixelStride = pixelStride;
-      outPlanes->planes[0].rowStride = gBuffer->getStride() * pixelStride;
-      return gBuffer->lockAsync(usage, usage, bounds, &outPlanes->planes[0].data, fence);
+      outPlanes->planes[0].pixelStride = bytesPerPixel;
+      outPlanes->planes[0].rowStride = bytesPerStride;
+      return result;
     }
 }
 
@@ -487,12 +564,6 @@
         return false;
     }
 
-    if (!AHardwareBuffer_isValidPixelFormat(desc->format)) {
-        ALOGE_IF(log, "Invalid AHardwareBuffer pixel format %u (%#x))",
-                desc->format, desc->format);
-        return false;
-    }
-
     if (desc->rfu0 != 0 || desc->rfu1 != 0) {
         ALOGE_IF(log, "AHardwareBuffer_Desc::rfu fields must be 0");
         return false;
@@ -557,114 +628,6 @@
     return true;
 }
 
-bool AHardwareBuffer_isValidPixelFormat(uint32_t format) {
-    static_assert(HAL_PIXEL_FORMAT_RGBA_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RGBX_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RGB_565 == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RGB_888 == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RGBA_FP16 == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RGBA_1010102 == AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_BLOB == AHARDWAREBUFFER_FORMAT_BLOB,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_DEPTH_16 == AHARDWAREBUFFER_FORMAT_D16_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_DEPTH_24 == AHARDWAREBUFFER_FORMAT_D24_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_DEPTH_24_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_DEPTH_32F == AHARDWAREBUFFER_FORMAT_D32_FLOAT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_DEPTH_32F_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_STENCIL_8 == AHARDWAREBUFFER_FORMAT_S8_UINT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_BGRA_8888 == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_YV12 == AHARDWAREBUFFER_FORMAT_YV12,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_Y8 == AHARDWAREBUFFER_FORMAT_Y8,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_Y16 == AHARDWAREBUFFER_FORMAT_Y16,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RAW16 == AHARDWAREBUFFER_FORMAT_RAW16,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RAW10 == AHARDWAREBUFFER_FORMAT_RAW10,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RAW12 == AHARDWAREBUFFER_FORMAT_RAW12,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_RAW_OPAQUE == AHARDWAREBUFFER_FORMAT_RAW_OPAQUE,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED == AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_YCBCR_420_888 == AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_YCBCR_422_SP == AHARDWAREBUFFER_FORMAT_YCbCr_422_SP,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_YCRCB_420_SP == AHARDWAREBUFFER_FORMAT_YCrCb_420_SP,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_YCBCR_422_I == AHARDWAREBUFFER_FORMAT_YCbCr_422_I,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
-                          AHARDWAREBUFFER_FORMAT_R8_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
-                          AHARDWAREBUFFER_FORMAT_R16_UINT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
-                          AHARDWAREBUFFER_FORMAT_R16G16_UINT,
-            "HAL and AHardwareBuffer pixel format don't match");
-    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
-                          AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
-            "HAL and AHardwareBuffer pixel format don't match");
-
-    switch (format) {
-        case AHARDWAREBUFFER_FORMAT_R8_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R16_UINT:
-        case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
-        case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
-        case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
-        case AHARDWAREBUFFER_FORMAT_BLOB:
-        case AHARDWAREBUFFER_FORMAT_D16_UNORM:
-        case AHARDWAREBUFFER_FORMAT_D24_UNORM:
-        case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
-        case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
-        case AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT:
-        case AHARDWAREBUFFER_FORMAT_S8_UINT:
-        case AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420:
-            // VNDK formats only -- unfortunately we can't differentiate from where we're called
-        case AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM:
-        case AHARDWAREBUFFER_FORMAT_YV12:
-        case AHARDWAREBUFFER_FORMAT_Y8:
-        case AHARDWAREBUFFER_FORMAT_Y16:
-        case AHARDWAREBUFFER_FORMAT_RAW16:
-        case AHARDWAREBUFFER_FORMAT_RAW10:
-        case AHARDWAREBUFFER_FORMAT_RAW12:
-        case AHARDWAREBUFFER_FORMAT_RAW_OPAQUE:
-        case AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED:
-        case AHARDWAREBUFFER_FORMAT_YCbCr_422_SP:
-        case AHARDWAREBUFFER_FORMAT_YCrCb_420_SP:
-        case AHARDWAREBUFFER_FORMAT_YCbCr_422_I:
-        case AHARDWAREBUFFER_FORMAT_YCbCr_P010:
-            return true;
-
-        default:
-            return false;
-    }
-}
-
 bool AHardwareBuffer_formatIsYuv(uint32_t format) {
     switch (format) {
         case AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420:
@@ -681,32 +644,6 @@
     }
 }
 
-uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format) {
-  switch (format) {
-      case AHARDWAREBUFFER_FORMAT_R8_UNORM:
-          return 1;
-      case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
-      case AHARDWAREBUFFER_FORMAT_D16_UNORM:
-      case AHARDWAREBUFFER_FORMAT_R16_UINT:
-          return 2;
-      case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
-      case AHARDWAREBUFFER_FORMAT_D24_UNORM:
-          return 3;
-      case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
-      case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
-      case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
-      case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
-      case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
-      case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
-          return 4;
-      case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
-      case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
-          return 8;
-      default:
-          return 0;
-  }
-}
-
 uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t hal_format) {
     return hal_format;
 }
diff --git a/libs/nativewindow/TEST_MAPPING b/libs/nativewindow/TEST_MAPPING
index bbad757..9d6425b 100644
--- a/libs/nativewindow/TEST_MAPPING
+++ b/libs/nativewindow/TEST_MAPPING
@@ -1,14 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "libnativewindow_test"
-    }
-  ],
-  "postsubmit": [
-    {
       "name": "libnativewindow_bindgen_test"
     },
     {
+      "name": "libnativewindow_test"
+    },
+    {
       "name": "libnativewindow_rs-internal_test"
     }
   ]
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index 6d3d295..880c694 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -37,15 +37,9 @@
 // parameters. Note: this does not verify any platform-specific contraints.
 bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool log);
 
-// whether this AHardwareBuffer format is valid
-bool AHardwareBuffer_isValidPixelFormat(uint32_t ahardwarebuffer_format);
-
 // whether this is a YUV type format
 bool AHardwareBuffer_formatIsYuv(uint32_t format);
 
-// number of bytes per pixel or 0 if unknown or multi-planar
-uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format);
-
 // convert AHardwareBuffer format to HAL format (note: this is a no-op)
 uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t format);
 
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index edaa422..b068f48 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1060,6 +1060,62 @@
     ANATIVEWINDOW_FRAME_RATE_MIN
 };
 
+/*
+ * Frame rate category values that can be used in Transaction::setFrameRateCategory.
+ */
+enum {
+    /**
+     * Default value. This value can also be set to return to default behavior, such as layers
+     * without animations.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT = 0,
+
+    /**
+     * The layer will explicitly not influence the frame rate.
+     * This may indicate a frame rate suitable for no animation updates (such as a cursor blinking
+     * or a sporadic update).
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE = 1,
+
+    /**
+     * Indicates a frame rate suitable for animations that looks fine even if played at a low frame
+     * rate.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_LOW = 2,
+
+    /**
+     * Indicates a middle frame rate suitable for animations that do not require higher frame
+     * rates, or do not benefit from high smoothness. This is normally 60 Hz or close to it.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3,
+
+    /**
+     * Indicates a frame rate suitable for animations that require a high frame rate, which may
+     * increase smoothness but may also increase power usage.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
+};
+
+/*
+ * Frame rate selection strategy values that can be used in
+ * Transaction::setFrameRateSelectionStrategy.
+ */
+enum {
+    /**
+     * Default value. The layer uses its own frame rate specifications, assuming it has any
+     * specifications, instead of its parent's.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 0,
+
+    /**
+     * The layer's frame rate specifications will propagate to and override those of its descendant
+     * layers.
+     * The layer with this strategy has the ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF
+     * behavior for itself.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+};
+
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
                                         int8_t compatibility, int8_t changeFrameRateStrategy) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index c2fd6ef..dcb5068 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -65,7 +65,6 @@
 LIBNATIVEWINDOW_PLATFORM {
   global:
     extern "C++" {
-      android::AHardwareBuffer_isValidPixelFormat*;
       android::AHardwareBuffer_convertFromPixelFormat*;
       android::AHardwareBuffer_convertToPixelFormat*;
       android::AHardwareBuffer_convertFromGrallocUsageBits*;
diff --git a/libs/permission/aidl/android/content/AttributionSourceState.aidl b/libs/permission/aidl/android/content/AttributionSourceState.aidl
index ed1b37d..b3fb7a7 100644
--- a/libs/permission/aidl/android/content/AttributionSourceState.aidl
+++ b/libs/permission/aidl/android/content/AttributionSourceState.aidl
@@ -27,6 +27,10 @@
     int pid = -1;
     /** The UID that is accessing the permission protected data. */
     int uid = -1;
+    /** The default device ID from where the permission protected data is read.
+     * @see Context#DEVICE_ID_DEFAULT
+     */
+    int deviceId = 0;
     /** The package that is accessing the permission protected data. */
     @nullable @utf8InCpp String packageName;
     /** The attribution tag of the app accessing the permission protected data. */
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 8d19c45..ba2eb7d 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -56,30 +56,8 @@
 filegroup {
     name: "librenderengine_sources",
     srcs: [
-        "Description.cpp",
         "ExternalTexture.cpp",
-        "Mesh.cpp",
         "RenderEngine.cpp",
-        "Texture.cpp",
-    ],
-}
-
-filegroup {
-    name: "librenderengine_gl_sources",
-    srcs: [
-        "gl/GLESRenderEngine.cpp",
-        "gl/GLExtensions.cpp",
-        "gl/GLFramebuffer.cpp",
-        "gl/GLImage.cpp",
-        "gl/GLShadowTexture.cpp",
-        "gl/GLShadowVertexGenerator.cpp",
-        "gl/GLSkiaShadowPort.cpp",
-        "gl/GLVertexBuffer.cpp",
-        "gl/ImageManager.cpp",
-        "gl/Program.cpp",
-        "gl/ProgramCache.cpp",
-        "gl/filters/BlurFilter.cpp",
-        "gl/filters/GenericProgram.cpp",
     ],
 }
 
@@ -96,6 +74,7 @@
         "skia/AutoBackendTexture.cpp",
         "skia/Cache.cpp",
         "skia/ColorSpaces.cpp",
+        "skia/GLExtensions.cpp",
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
         "skia/SkiaVkRenderEngine.cpp",
@@ -136,7 +115,6 @@
     ],
     srcs: [
         ":librenderengine_sources",
-        ":librenderengine_gl_sources",
         ":librenderengine_threaded_sources",
         ":librenderengine_skia_sources",
     ],
@@ -155,8 +133,6 @@
     name: "librenderengine_mocks",
     defaults: ["librenderengine_defaults"],
     srcs: [
-        "mock/Framebuffer.cpp",
-        "mock/Image.cpp",
         "mock/RenderEngine.cpp",
     ],
     static_libs: [
diff --git a/libs/renderengine/Description.cpp b/libs/renderengine/Description.cpp
deleted file mode 100644
index 245c9e1..0000000
--- a/libs/renderengine/Description.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/private/Description.h>
-
-#include <stdint.h>
-
-#include <utils/TypeHelpers.h>
-
-namespace android {
-namespace renderengine {
-
-Description::TransferFunction Description::dataSpaceToTransferFunction(ui::Dataspace dataSpace) {
-    ui::Dataspace transfer = static_cast<ui::Dataspace>(dataSpace & ui::Dataspace::TRANSFER_MASK);
-    switch (transfer) {
-        case ui::Dataspace::TRANSFER_ST2084:
-            return Description::TransferFunction::ST2084;
-        case ui::Dataspace::TRANSFER_HLG:
-            return Description::TransferFunction::HLG;
-        case ui::Dataspace::TRANSFER_LINEAR:
-            return Description::TransferFunction::LINEAR;
-        default:
-            return Description::TransferFunction::SRGB;
-    }
-}
-
-bool Description::hasInputTransformMatrix() const {
-    const mat4 identity;
-    return inputTransformMatrix != identity;
-}
-
-bool Description::hasOutputTransformMatrix() const {
-    const mat4 identity;
-    return outputTransformMatrix != identity;
-}
-
-bool Description::hasColorMatrix() const {
-    const mat4 identity;
-    return colorMatrix != identity;
-}
-
-bool Description::hasDisplayColorMatrix() const {
-    const mat4 identity;
-    return displayColorMatrix != identity;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index 9eb42cd..6f2a96a 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -27,14 +27,6 @@
       : mBuffer(buffer), mRenderEngine(renderEngine), mWritable(usage & WRITEABLE) {
     LOG_ALWAYS_FATAL_IF(buffer == nullptr,
                         "Attempted to bind a null buffer to an external texture!");
-    // GLESRenderEngine has a separate texture cache for output buffers,
-    if (usage == WRITEABLE &&
-        (mRenderEngine.getRenderEngineType() ==
-                 renderengine::RenderEngine::RenderEngineType::GLES ||
-         mRenderEngine.getRenderEngineType() ==
-                 renderengine::RenderEngine::RenderEngineType::THREADED)) {
-        return;
-    }
     mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable);
 }
 
diff --git a/libs/renderengine/Mesh.cpp b/libs/renderengine/Mesh.cpp
deleted file mode 100644
index ed2f45f..0000000
--- a/libs/renderengine/Mesh.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/Mesh.h>
-
-#include <utils/Log.h>
-
-namespace android {
-namespace renderengine {
-
-Mesh::Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
-           size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize,
-           size_t indexCount)
-      : mVertexCount(vertexCount),
-        mVertexSize(vertexSize),
-        mTexCoordsSize(texCoordSize),
-        mCropCoordsSize(cropCoordsSize),
-        mShadowColorSize(shadowColorSize),
-        mShadowParamsSize(shadowParamsSize),
-        mPrimitive(primitive),
-        mIndexCount(indexCount) {
-    if (vertexCount == 0) {
-        mVertices.resize(1);
-        mVertices[0] = 0.0f;
-        mStride = 0;
-        return;
-    }
-    size_t stride = vertexSize + texCoordSize + cropCoordsSize + shadowColorSize + shadowParamsSize;
-    size_t remainder = (stride * vertexCount) / vertexCount;
-    // Since all of the input parameters are unsigned, if stride is less than
-    // either vertexSize or texCoordSize, it must have overflowed. remainder
-    // will be equal to stride as long as stride * vertexCount doesn't overflow.
-    if ((stride < vertexSize) || (remainder != stride)) {
-        ALOGE("Overflow in Mesh(..., %zu, %zu, %zu, %zu, %zu, %zu)", vertexCount, vertexSize,
-              texCoordSize, cropCoordsSize, shadowColorSize, shadowParamsSize);
-        mVertices.resize(1);
-        mVertices[0] = 0.0f;
-        mVertexCount = 0;
-        mVertexSize = 0;
-        mTexCoordsSize = 0;
-        mCropCoordsSize = 0;
-        mShadowColorSize = 0;
-        mShadowParamsSize = 0;
-        mStride = 0;
-        return;
-    }
-
-    mVertices.resize(stride * vertexCount);
-    mStride = stride;
-    mIndices.resize(indexCount);
-}
-
-Mesh::Primitive Mesh::getPrimitive() const {
-    return mPrimitive;
-}
-
-float const* Mesh::getPositions() const {
-    return mVertices.data();
-}
-float* Mesh::getPositions() {
-    return mVertices.data();
-}
-
-float const* Mesh::getTexCoords() const {
-    return mVertices.data() + mVertexSize;
-}
-float* Mesh::getTexCoords() {
-    return mVertices.data() + mVertexSize;
-}
-
-float const* Mesh::getCropCoords() const {
-    return mVertices.data() + mVertexSize + mTexCoordsSize;
-}
-float* Mesh::getCropCoords() {
-    return mVertices.data() + mVertexSize + mTexCoordsSize;
-}
-
-float const* Mesh::getShadowColor() const {
-    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
-}
-float* Mesh::getShadowColor() {
-    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
-}
-
-float const* Mesh::getShadowParams() const {
-    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
-}
-float* Mesh::getShadowParams() {
-    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
-}
-
-uint16_t const* Mesh::getIndices() const {
-    return mIndices.data();
-}
-
-uint16_t* Mesh::getIndices() {
-    return mIndices.data();
-}
-
-size_t Mesh::getVertexCount() const {
-    return mVertexCount;
-}
-
-size_t Mesh::getVertexSize() const {
-    return mVertexSize;
-}
-
-size_t Mesh::getTexCoordsSize() const {
-    return mTexCoordsSize;
-}
-
-size_t Mesh::getShadowColorSize() const {
-    return mShadowColorSize;
-}
-
-size_t Mesh::getShadowParamsSize() const {
-    return mShadowParamsSize;
-}
-
-size_t Mesh::getByteStride() const {
-    return mStride * sizeof(float);
-}
-
-size_t Mesh::getStride() const {
-    return mStride;
-}
-
-size_t Mesh::getIndexCount() const {
-    return mIndexCount;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index d08c221..3e1ac33 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -18,7 +18,6 @@
 
 #include <cutils/properties.h>
 #include <log/log.h>
-#include "gl/GLESRenderEngine.h"
 #include "renderengine/ExternalTexture.h"
 #include "threaded/RenderEngineThreaded.h"
 
@@ -30,11 +29,6 @@
 
 std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
     switch (args.renderEngineType) {
-        case RenderEngineType::THREADED:
-            ALOGD("Threaded RenderEngine with GLES Backend");
-            return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); },
-                    args.renderEngineType);
         case RenderEngineType::SKIA_GL:
             ALOGD("RenderEngine with SkiaGL Backend");
             return renderengine::skia::SkiaGLRenderEngine::create(args);
@@ -56,10 +50,6 @@
                         return android::renderengine::skia::SkiaVkRenderEngine::create(args);
                     },
                     args.renderEngineType);
-        case RenderEngineType::GLES:
-        default:
-            ALOGD("RenderEngine with GLES Backend");
-            return renderengine::gl::GLESRenderEngine::create(args);
     }
 }
 
@@ -78,13 +68,11 @@
 ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display,
                                                   const std::vector<LayerSettings>& layers,
                                                   const std::shared_ptr<ExternalTexture>& buffer,
-                                                  const bool useFramebufferCache,
                                                   base::unique_fd&& bufferFence) {
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
     updateProtectedContext(layers, buffer);
-    drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
-                       std::move(bufferFence));
+    drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
     return resultFuture;
 }
 
diff --git a/libs/renderengine/Texture.cpp b/libs/renderengine/Texture.cpp
deleted file mode 100644
index 154cde8..0000000
--- a/libs/renderengine/Texture.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/Texture.h>
-
-namespace android {
-namespace renderengine {
-
-Texture::Texture()
-      : mTextureName(0), mTextureTarget(TEXTURE_2D), mWidth(0), mHeight(0), mFiltering(false) {}
-
-Texture::Texture(Target textureTarget, uint32_t textureName)
-      : mTextureName(textureName),
-        mTextureTarget(textureTarget),
-        mWidth(0),
-        mHeight(0),
-        mFiltering(false) {}
-
-void Texture::init(Target textureTarget, uint32_t textureName) {
-    mTextureName = textureName;
-    mTextureTarget = textureTarget;
-}
-
-Texture::~Texture() {}
-
-void Texture::setMatrix(float const* matrix) {
-    mTextureMatrix = mat4(matrix);
-}
-
-void Texture::setFiltering(bool enabled) {
-    mFiltering = enabled;
-}
-
-void Texture::setDimensions(size_t width, size_t height) {
-    mWidth = width;
-    mHeight = height;
-}
-
-uint32_t Texture::getTextureName() const {
-    return mTextureName;
-}
-
-uint32_t Texture::getTextureTarget() const {
-    return mTextureTarget;
-}
-
-const mat4& Texture::getMatrix() const {
-    return mTextureMatrix;
-}
-
-bool Texture::getFiltering() const {
-    return mFiltering;
-}
-
-size_t Texture::getWidth() const {
-    return mWidth;
-}
-
-size_t Texture::getHeight() const {
-    return mHeight;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index bd7b617..a7f1df9 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -43,10 +43,6 @@
             return "skiavk";
         case RenderEngine::RenderEngineType::SKIA_VK_THREADED:
             return "skiavkthreaded";
-        case RenderEngine::RenderEngineType::GLES:
-        case RenderEngine::RenderEngineType::THREADED:
-            LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?");
-            return "unused";
     }
 }
 
@@ -108,10 +104,6 @@
     return std::pair<uint32_t, uint32_t>(width, height);
 }
 
-// This value doesn't matter, as it's not read. TODO(b/199918329): Once we remove
-// GLESRenderEngine we can remove this, too.
-static constexpr const bool kUseFrameBufferCache = false;
-
 static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
     auto args = RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
@@ -121,7 +113,6 @@
                         .setSupportsBackgroundBlur(true)
                         .setContextPriority(RenderEngine::ContextPriority::REALTIME)
                         .setRenderEngineType(type)
-                        .setUseColorManagerment(true)
                         .build();
     return RenderEngine::create(args);
 }
@@ -173,10 +164,7 @@
     };
     auto layers = std::vector<LayerSettings>{layer};
 
-    sp<Fence> waitFence =
-            re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd())
-                    .get()
-                    .value();
+    sp<Fence> waitFence = re.drawLayers(display, layers, texture, base::unique_fd()).get().value();
     waitFence->waitForever(LOG_TAG);
     return texture;
 }
@@ -205,10 +193,8 @@
 
     // This loop starts and stops the timer.
     for (auto _ : benchState) {
-        sp<Fence> waitFence = re.drawLayers(display, layers, outputBuffer, kUseFrameBufferCache,
-                                            base::unique_fd())
-                                      .get()
-                                      .value();
+        sp<Fence> waitFence =
+                re.drawLayers(display, layers, outputBuffer, base::unique_fd()).get().value();
         waitFence->waitForever(LOG_TAG);
     }
 
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
deleted file mode 100644
index 0d7df10..0000000
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ /dev/null
@@ -1,1866 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#include "EGL/egl.h"
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <sched.h>
-#include <cmath>
-#include <fstream>
-#include <sstream>
-#include <unordered_set>
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <android-base/stringprintf.h>
-#include <cutils/compiler.h>
-#include <cutils/properties.h>
-#include <gui/DebugEGLImageTracker.h>
-#include <renderengine/Mesh.h>
-#include <renderengine/Texture.h>
-#include <renderengine/private/Description.h>
-#include <sync/sync.h>
-#include <ui/ColorSpace.h>
-#include <ui/DebugUtils.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/Rect.h>
-#include <ui/Region.h>
-#include <utils/KeyedVector.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "GLExtensions.h"
-#include "GLFramebuffer.h"
-#include "GLImage.h"
-#include "GLShadowVertexGenerator.h"
-#include "Program.h"
-#include "ProgramCache.h"
-#include "filters/BlurFilter.h"
-
-bool checkGlError(const char* op, int lineNumber) {
-    bool errorFound = false;
-    GLint error = glGetError();
-    while (error != GL_NO_ERROR) {
-        errorFound = true;
-        error = glGetError();
-        ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
-    }
-    return errorFound;
-}
-
-static constexpr bool outputDebugPPMs = false;
-
-void writePPM(const char* basename, GLuint width, GLuint height) {
-    ALOGV("writePPM #%s: %d x %d", basename, width, height);
-
-    std::vector<GLubyte> pixels(width * height * 4);
-    std::vector<GLubyte> outBuffer(width * height * 3);
-
-    // TODO(courtneygo): We can now have float formats, need
-    // to remove this code or update to support.
-    // Make returned pixels fit in uint32_t, one byte per component
-    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
-    if (checkGlError(__FUNCTION__, __LINE__)) {
-        return;
-    }
-
-    std::string filename(basename);
-    filename.append(".ppm");
-    std::ofstream file(filename.c_str(), std::ios::binary);
-    if (!file.is_open()) {
-        ALOGE("Unable to open file: %s", filename.c_str());
-        ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
-              "surfaceflinger to write debug images");
-        return;
-    }
-
-    file << "P6\n";
-    file << width << "\n";
-    file << height << "\n";
-    file << 255 << "\n";
-
-    auto ptr = reinterpret_cast<char*>(pixels.data());
-    auto outPtr = reinterpret_cast<char*>(outBuffer.data());
-    for (int y = height - 1; y >= 0; y--) {
-        char* data = ptr + y * width * sizeof(uint32_t);
-
-        for (GLuint x = 0; x < width; x++) {
-            // Only copy R, G and B components
-            outPtr[0] = data[0];
-            outPtr[1] = data[1];
-            outPtr[2] = data[2];
-            data += sizeof(uint32_t);
-            outPtr += 3;
-        }
-    }
-    file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
-}
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class BindNativeBufferAsFramebuffer {
-public:
-    BindNativeBufferAsFramebuffer(GLESRenderEngine& engine, ANativeWindowBuffer* buffer,
-                                  const bool useFramebufferCache)
-          : mEngine(engine), mFramebuffer(mEngine.getFramebufferForDrawing()), mStatus(NO_ERROR) {
-        mStatus = mFramebuffer->setNativeWindowBuffer(buffer, mEngine.isProtected(),
-                                                      useFramebufferCache)
-                ? mEngine.bindFrameBuffer(mFramebuffer)
-                : NO_MEMORY;
-    }
-    ~BindNativeBufferAsFramebuffer() {
-        mFramebuffer->setNativeWindowBuffer(nullptr, false, /*arbitrary*/ true);
-        mEngine.unbindFrameBuffer(mFramebuffer);
-    }
-    status_t getStatus() const { return mStatus; }
-
-private:
-    GLESRenderEngine& mEngine;
-    Framebuffer* mFramebuffer;
-    status_t mStatus;
-};
-
-using base::StringAppendF;
-using ui::Dataspace;
-
-static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute,
-                                         EGLint wanted, EGLConfig* outConfig) {
-    EGLint numConfigs = -1, n = 0;
-    eglGetConfigs(dpy, nullptr, 0, &numConfigs);
-    std::vector<EGLConfig> configs(numConfigs, EGL_NO_CONFIG_KHR);
-    eglChooseConfig(dpy, attrs, configs.data(), configs.size(), &n);
-    configs.resize(n);
-
-    if (!configs.empty()) {
-        if (attribute != EGL_NONE) {
-            for (EGLConfig config : configs) {
-                EGLint value = 0;
-                eglGetConfigAttrib(dpy, config, attribute, &value);
-                if (wanted == value) {
-                    *outConfig = config;
-                    return NO_ERROR;
-                }
-            }
-        } else {
-            // just pick the first one
-            *outConfig = configs[0];
-            return NO_ERROR;
-        }
-    }
-
-    return NAME_NOT_FOUND;
-}
-
-static status_t selectEGLConfig(EGLDisplay display, EGLint format, EGLint renderableType,
-                                EGLConfig* config) {
-    // select our EGLConfig. It must support EGL_RECORDABLE_ANDROID if
-    // it is to be used with WIFI displays
-    status_t err;
-    EGLint wantedAttribute;
-    EGLint wantedAttributeValue;
-
-    std::vector<EGLint> attribs;
-    if (renderableType) {
-        const ui::PixelFormat pixelFormat = static_cast<ui::PixelFormat>(format);
-        const bool is1010102 = pixelFormat == ui::PixelFormat::RGBA_1010102;
-
-        // Default to 8 bits per channel.
-        const EGLint tmpAttribs[] = {
-                EGL_RENDERABLE_TYPE,
-                renderableType,
-                EGL_RECORDABLE_ANDROID,
-                EGL_TRUE,
-                EGL_SURFACE_TYPE,
-                EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
-                EGL_FRAMEBUFFER_TARGET_ANDROID,
-                EGL_TRUE,
-                EGL_RED_SIZE,
-                is1010102 ? 10 : 8,
-                EGL_GREEN_SIZE,
-                is1010102 ? 10 : 8,
-                EGL_BLUE_SIZE,
-                is1010102 ? 10 : 8,
-                EGL_ALPHA_SIZE,
-                is1010102 ? 2 : 8,
-                EGL_NONE,
-        };
-        std::copy(tmpAttribs, tmpAttribs + (sizeof(tmpAttribs) / sizeof(EGLint)),
-                  std::back_inserter(attribs));
-        wantedAttribute = EGL_NONE;
-        wantedAttributeValue = EGL_NONE;
-    } else {
-        // if no renderable type specified, fallback to a simplified query
-        wantedAttribute = EGL_NATIVE_VISUAL_ID;
-        wantedAttributeValue = format;
-    }
-
-    err = selectConfigForAttribute(display, attribs.data(), wantedAttribute, wantedAttributeValue,
-                                   config);
-    if (err == NO_ERROR) {
-        EGLint caveat;
-        if (eglGetConfigAttrib(display, *config, EGL_CONFIG_CAVEAT, &caveat))
-            ALOGW_IF(caveat == EGL_SLOW_CONFIG, "EGL_SLOW_CONFIG selected!");
-    }
-
-    return err;
-}
-
-std::optional<RenderEngine::ContextPriority> GLESRenderEngine::createContextPriority(
-        const RenderEngineCreationArgs& args) {
-    if (!GLExtensions::getInstance().hasContextPriority()) {
-        return std::nullopt;
-    }
-
-    switch (args.contextPriority) {
-        case RenderEngine::ContextPriority::REALTIME:
-            if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
-                return RenderEngine::ContextPriority::REALTIME;
-            } else {
-                ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
-                return RenderEngine::ContextPriority::HIGH;
-            }
-        case RenderEngine::ContextPriority::HIGH:
-        case RenderEngine::ContextPriority::MEDIUM:
-        case RenderEngine::ContextPriority::LOW:
-            return args.contextPriority;
-        default:
-            return std::nullopt;
-    }
-}
-
-std::unique_ptr<GLESRenderEngine> GLESRenderEngine::create(const RenderEngineCreationArgs& args) {
-    // initialize EGL for the default display
-    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    if (!eglInitialize(display, nullptr, nullptr)) {
-        LOG_ALWAYS_FATAL("failed to initialize EGL. EGL error=0x%x", eglGetError());
-    }
-
-    const auto eglVersion = eglQueryString(display, EGL_VERSION);
-    if (!eglVersion) {
-        checkGlError(__FUNCTION__, __LINE__);
-        LOG_ALWAYS_FATAL("eglQueryString(EGL_VERSION) failed");
-    }
-
-    // Use the Android impl to grab EGL_NV_context_priority_realtime
-    const auto eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
-    if (!eglExtensions) {
-        checkGlError(__FUNCTION__, __LINE__);
-        LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
-    }
-
-    GLExtensions& extensions = GLExtensions::getInstance();
-    extensions.initWithEGLStrings(eglVersion, eglExtensions);
-
-    // The code assumes that ES2 or later is available if this extension is
-    // supported.
-    EGLConfig config = EGL_NO_CONFIG;
-    if (!extensions.hasNoConfigContext()) {
-        config = chooseEglConfig(display, args.pixelFormat, /*logConfig*/ true);
-    }
-
-    const std::optional<RenderEngine::ContextPriority> priority = createContextPriority(args);
-    EGLContext protectedContext = EGL_NO_CONTEXT;
-    if (args.enableProtectedContext && extensions.hasProtectedContent()) {
-        protectedContext =
-                createEglContext(display, config, nullptr, priority, Protection::PROTECTED);
-        ALOGE_IF(protectedContext == EGL_NO_CONTEXT, "Can't create protected context");
-    }
-
-    EGLContext ctxt =
-            createEglContext(display, config, protectedContext, priority, Protection::UNPROTECTED);
-
-    // if can't create a GL context, we can only abort.
-    LOG_ALWAYS_FATAL_IF(ctxt == EGL_NO_CONTEXT, "EGLContext creation failed");
-
-    EGLSurface stub = EGL_NO_SURFACE;
-    if (!extensions.hasSurfacelessContext()) {
-        stub = createStubEglPbufferSurface(display, config, args.pixelFormat,
-                                           Protection::UNPROTECTED);
-        LOG_ALWAYS_FATAL_IF(stub == EGL_NO_SURFACE, "can't create stub pbuffer");
-    }
-    EGLBoolean success = eglMakeCurrent(display, stub, stub, ctxt);
-    LOG_ALWAYS_FATAL_IF(!success, "can't make stub pbuffer current");
-    extensions.initWithGLStrings(glGetString(GL_VENDOR), glGetString(GL_RENDERER),
-                                 glGetString(GL_VERSION), glGetString(GL_EXTENSIONS));
-
-    EGLSurface protectedStub = EGL_NO_SURFACE;
-    if (protectedContext != EGL_NO_CONTEXT && !extensions.hasSurfacelessContext()) {
-        protectedStub = createStubEglPbufferSurface(display, config, args.pixelFormat,
-                                                    Protection::PROTECTED);
-        ALOGE_IF(protectedStub == EGL_NO_SURFACE, "can't create protected stub pbuffer");
-    }
-
-    // now figure out what version of GL did we actually get
-    GlesVersion version = parseGlesVersion(extensions.getVersion());
-
-    LOG_ALWAYS_FATAL_IF(args.supportsBackgroundBlur && version < GLES_VERSION_3_0,
-        "Blurs require OpenGL ES 3.0. Please unset ro.surface_flinger.supports_background_blur");
-
-    // initialize the renderer while GL is current
-    std::unique_ptr<GLESRenderEngine> engine;
-    switch (version) {
-        case GLES_VERSION_1_0:
-        case GLES_VERSION_1_1:
-            LOG_ALWAYS_FATAL("SurfaceFlinger requires OpenGL ES 2.0 minimum to run.");
-            break;
-        case GLES_VERSION_2_0:
-        case GLES_VERSION_3_0:
-            engine = std::make_unique<GLESRenderEngine>(args, display, config, ctxt, stub,
-                                                        protectedContext, protectedStub);
-            break;
-    }
-
-    ALOGI("OpenGL ES informations:");
-    ALOGI("vendor    : %s", extensions.getVendor());
-    ALOGI("renderer  : %s", extensions.getRenderer());
-    ALOGI("version   : %s", extensions.getVersion());
-    ALOGI("extensions: %s", extensions.getExtensions());
-    ALOGI("GL_MAX_TEXTURE_SIZE = %zu", engine->getMaxTextureSize());
-    ALOGI("GL_MAX_VIEWPORT_DIMS = %zu", engine->getMaxViewportDims());
-    return engine;
-}
-
-EGLConfig GLESRenderEngine::chooseEglConfig(EGLDisplay display, int format, bool logConfig) {
-    status_t err;
-    EGLConfig config;
-
-    // First try to get an ES3 config
-    err = selectEGLConfig(display, format, EGL_OPENGL_ES3_BIT, &config);
-    if (err != NO_ERROR) {
-        // If ES3 fails, try to get an ES2 config
-        err = selectEGLConfig(display, format, EGL_OPENGL_ES2_BIT, &config);
-        if (err != NO_ERROR) {
-            // If ES2 still doesn't work, probably because we're on the emulator.
-            // try a simplified query
-            ALOGW("no suitable EGLConfig found, trying a simpler query");
-            err = selectEGLConfig(display, format, 0, &config);
-            if (err != NO_ERROR) {
-                // this EGL is too lame for android
-                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up");
-            }
-        }
-    }
-
-    if (logConfig) {
-        // print some debugging info
-        EGLint r, g, b, a;
-        eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r);
-        eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g);
-        eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b);
-        eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a);
-        ALOGI("EGL information:");
-        ALOGI("vendor    : %s", eglQueryString(display, EGL_VENDOR));
-        ALOGI("version   : %s", eglQueryString(display, EGL_VERSION));
-        ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS));
-        ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported");
-        ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config);
-    }
-
-    return config;
-}
-
-GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
-                                   EGLConfig config, EGLContext ctxt, EGLSurface stub,
-                                   EGLContext protectedContext, EGLSurface protectedStub)
-      : RenderEngine(args.renderEngineType),
-        mEGLDisplay(display),
-        mEGLConfig(config),
-        mEGLContext(ctxt),
-        mStubSurface(stub),
-        mProtectedEGLContext(protectedContext),
-        mProtectedStubSurface(protectedStub),
-        mVpWidth(0),
-        mVpHeight(0),
-        mFramebufferImageCacheSize(args.imageCacheSize),
-        mUseColorManagement(args.useColorManagement),
-        mPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly) {
-    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
-    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-    glPixelStorei(GL_PACK_ALIGNMENT, 4);
-
-    // Initialize protected EGL Context.
-    if (mProtectedEGLContext != EGL_NO_CONTEXT) {
-        EGLBoolean success = eglMakeCurrent(display, mProtectedStubSurface, mProtectedStubSurface,
-                                            mProtectedEGLContext);
-        ALOGE_IF(!success, "can't make protected context current");
-        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-        glPixelStorei(GL_PACK_ALIGNMENT, 4);
-        success = eglMakeCurrent(display, mStubSurface, mStubSurface, mEGLContext);
-        LOG_ALWAYS_FATAL_IF(!success, "can't make default context current");
-    }
-
-    // mColorBlindnessCorrection = M;
-
-    if (mUseColorManagement) {
-        const ColorSpace srgb(ColorSpace::sRGB());
-        const ColorSpace displayP3(ColorSpace::DisplayP3());
-        const ColorSpace bt2020(ColorSpace::BT2020());
-
-        // no chromatic adaptation needed since all color spaces use D65 for their white points.
-        mSrgbToXyz = mat4(srgb.getRGBtoXYZ());
-        mDisplayP3ToXyz = mat4(displayP3.getRGBtoXYZ());
-        mBt2020ToXyz = mat4(bt2020.getRGBtoXYZ());
-        mXyzToSrgb = mat4(srgb.getXYZtoRGB());
-        mXyzToDisplayP3 = mat4(displayP3.getXYZtoRGB());
-        mXyzToBt2020 = mat4(bt2020.getXYZtoRGB());
-
-        // Compute sRGB to Display P3 and BT2020 transform matrix.
-        // NOTE: For now, we are limiting output wide color space support to
-        // Display-P3 and BT2020 only.
-        mSrgbToDisplayP3 = mXyzToDisplayP3 * mSrgbToXyz;
-        mSrgbToBt2020 = mXyzToBt2020 * mSrgbToXyz;
-
-        // Compute Display P3 to sRGB and BT2020 transform matrix.
-        mDisplayP3ToSrgb = mXyzToSrgb * mDisplayP3ToXyz;
-        mDisplayP3ToBt2020 = mXyzToBt2020 * mDisplayP3ToXyz;
-
-        // Compute BT2020 to sRGB and Display P3 transform matrix
-        mBt2020ToSrgb = mXyzToSrgb * mBt2020ToXyz;
-        mBt2020ToDisplayP3 = mXyzToDisplayP3 * mBt2020ToXyz;
-    }
-
-    char value[PROPERTY_VALUE_MAX];
-    property_get("debug.egl.traceGpuCompletion", value, "0");
-    if (atoi(value)) {
-        mTraceGpuCompletion = true;
-        mFlushTracer = std::make_unique<FlushTracer>(this);
-    }
-
-    if (args.supportsBackgroundBlur) {
-        mBlurFilter = new BlurFilter(*this);
-        checkErrors("BlurFilter creation");
-    }
-
-    mImageManager = std::make_unique<ImageManager>(this);
-    mImageManager->initThread();
-    mDrawingBuffer = createFramebuffer();
-    sp<GraphicBuffer> buf =
-            sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBA_8888, 1,
-                                    GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE,
-                                    "placeholder");
-
-    const status_t err = buf->initCheck();
-    if (err != OK) {
-        ALOGE("Error allocating placeholder buffer: %d", err);
-        return;
-    }
-    mPlaceholderBuffer = buf.get();
-    EGLint attributes[] = {
-            EGL_NONE,
-    };
-    mPlaceholderImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
-                                          mPlaceholderBuffer, attributes);
-    ALOGE_IF(mPlaceholderImage == EGL_NO_IMAGE_KHR, "Failed to create placeholder image: %#x",
-             eglGetError());
-
-    mShadowTexture = std::make_unique<GLShadowTexture>();
-}
-
-GLESRenderEngine::~GLESRenderEngine() {
-    // Destroy the image manager first.
-    mImageManager = nullptr;
-    mShadowTexture = nullptr;
-    cleanFramebufferCache();
-    ProgramCache::getInstance().purgeCaches();
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    glDisableVertexAttribArray(Program::position);
-    unbindFrameBuffer(mDrawingBuffer.get());
-    mDrawingBuffer = nullptr;
-    eglDestroyImageKHR(mEGLDisplay, mPlaceholderImage);
-    mImageCache.clear();
-    if (mStubSurface != EGL_NO_SURFACE) {
-        eglDestroySurface(mEGLDisplay, mStubSurface);
-    }
-    if (mProtectedStubSurface != EGL_NO_SURFACE) {
-        eglDestroySurface(mEGLDisplay, mProtectedStubSurface);
-    }
-    if (mEGLContext != EGL_NO_CONTEXT) {
-        eglDestroyContext(mEGLDisplay, mEGLContext);
-    }
-    if (mProtectedEGLContext != EGL_NO_CONTEXT) {
-        eglDestroyContext(mEGLDisplay, mProtectedEGLContext);
-    }
-    eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-    eglTerminate(mEGLDisplay);
-    eglReleaseThread();
-}
-
-std::unique_ptr<Framebuffer> GLESRenderEngine::createFramebuffer() {
-    return std::make_unique<GLFramebuffer>(*this);
-}
-
-std::unique_ptr<Image> GLESRenderEngine::createImage() {
-    return std::make_unique<GLImage>(*this);
-}
-
-Framebuffer* GLESRenderEngine::getFramebufferForDrawing() {
-    return mDrawingBuffer.get();
-}
-
-std::future<void> GLESRenderEngine::primeCache() {
-    ProgramCache::getInstance().primeCache(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
-                                           mUseColorManagement, mPrecacheToneMapperShaderOnly);
-    return {};
-}
-
-base::unique_fd GLESRenderEngine::flush() {
-    ATRACE_CALL();
-    if (!GLExtensions::getInstance().hasNativeFenceSync()) {
-        return base::unique_fd();
-    }
-
-    EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
-    if (sync == EGL_NO_SYNC_KHR) {
-        ALOGW("failed to create EGL native fence sync: %#x", eglGetError());
-        return base::unique_fd();
-    }
-
-    // native fence fd will not be populated until flush() is done.
-    glFlush();
-
-    // get the fence fd
-    base::unique_fd fenceFd(eglDupNativeFenceFDANDROID(mEGLDisplay, sync));
-    eglDestroySyncKHR(mEGLDisplay, sync);
-    if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
-        ALOGW("failed to dup EGL native fence sync: %#x", eglGetError());
-    }
-
-    // Only trace if we have a valid fence, as current usage falls back to
-    // calling finish() if the fence fd is invalid.
-    if (CC_UNLIKELY(mTraceGpuCompletion && mFlushTracer) && fenceFd.get() >= 0) {
-        mFlushTracer->queueSync(eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr));
-    }
-
-    return fenceFd;
-}
-
-bool GLESRenderEngine::finish() {
-    ATRACE_CALL();
-    if (!GLExtensions::getInstance().hasFenceSync()) {
-        ALOGW("no synchronization support");
-        return false;
-    }
-
-    EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr);
-    if (sync == EGL_NO_SYNC_KHR) {
-        ALOGW("failed to create EGL fence sync: %#x", eglGetError());
-        return false;
-    }
-
-    if (CC_UNLIKELY(mTraceGpuCompletion && mFlushTracer)) {
-        mFlushTracer->queueSync(eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr));
-    }
-
-    return waitSync(sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR);
-}
-
-bool GLESRenderEngine::waitSync(EGLSyncKHR sync, EGLint flags) {
-    EGLint result = eglClientWaitSyncKHR(mEGLDisplay, sync, flags, 2000000000 /*2 sec*/);
-    EGLint error = eglGetError();
-    eglDestroySyncKHR(mEGLDisplay, sync);
-    if (result != EGL_CONDITION_SATISFIED_KHR) {
-        if (result == EGL_TIMEOUT_EXPIRED_KHR) {
-            ALOGW("fence wait timed out");
-        } else {
-            ALOGW("error waiting on EGL fence: %#x", error);
-        }
-        return false;
-    }
-
-    return true;
-}
-
-bool GLESRenderEngine::waitFence(base::unique_fd fenceFd) {
-    if (!GLExtensions::getInstance().hasNativeFenceSync() ||
-        !GLExtensions::getInstance().hasWaitSync()) {
-        return false;
-    }
-
-    // release the fd and transfer the ownership to EGLSync
-    EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd.release(), EGL_NONE};
-    EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
-    if (sync == EGL_NO_SYNC_KHR) {
-        ALOGE("failed to create EGL native fence sync: %#x", eglGetError());
-        return false;
-    }
-
-    // XXX: The spec draft is inconsistent as to whether this should return an
-    // EGLint or void.  Ignore the return value for now, as it's not strictly
-    // needed.
-    eglWaitSyncKHR(mEGLDisplay, sync, 0);
-    EGLint error = eglGetError();
-    eglDestroySyncKHR(mEGLDisplay, sync);
-    if (error != EGL_SUCCESS) {
-        ALOGE("failed to wait for EGL native fence sync: %#x", error);
-        return false;
-    }
-
-    return true;
-}
-
-void GLESRenderEngine::clearWithColor(float red, float green, float blue, float alpha) {
-    ATRACE_CALL();
-    glDisable(GL_BLEND);
-    glClearColor(red, green, blue, alpha);
-    glClear(GL_COLOR_BUFFER_BIT);
-}
-
-void GLESRenderEngine::fillRegionWithColor(const Region& region, float red, float green, float blue,
-                                           float alpha) {
-    size_t c;
-    Rect const* r = region.getArray(&c);
-    Mesh mesh = Mesh::Builder()
-                        .setPrimitive(Mesh::TRIANGLES)
-                        .setVertices(c * 6 /* count */, 2 /* size */)
-                        .build();
-    Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
-    for (size_t i = 0; i < c; i++, r++) {
-        position[i * 6 + 0].x = r->left;
-        position[i * 6 + 0].y = r->top;
-        position[i * 6 + 1].x = r->left;
-        position[i * 6 + 1].y = r->bottom;
-        position[i * 6 + 2].x = r->right;
-        position[i * 6 + 2].y = r->bottom;
-        position[i * 6 + 3].x = r->left;
-        position[i * 6 + 3].y = r->top;
-        position[i * 6 + 4].x = r->right;
-        position[i * 6 + 4].y = r->bottom;
-        position[i * 6 + 5].x = r->right;
-        position[i * 6 + 5].y = r->top;
-    }
-    setupFillWithColor(red, green, blue, alpha);
-    drawMesh(mesh);
-}
-
-void GLESRenderEngine::setScissor(const Rect& region) {
-    glScissor(region.left, region.top, region.getWidth(), region.getHeight());
-    glEnable(GL_SCISSOR_TEST);
-}
-
-void GLESRenderEngine::disableScissor() {
-    glDisable(GL_SCISSOR_TEST);
-}
-
-void GLESRenderEngine::genTextures(size_t count, uint32_t* names) {
-    glGenTextures(count, names);
-}
-
-void GLESRenderEngine::deleteTextures(size_t count, uint32_t const* names) {
-    for (int i = 0; i < count; ++i) {
-        mTextureView.erase(names[i]);
-    }
-    glDeleteTextures(count, names);
-}
-
-void GLESRenderEngine::bindExternalTextureImage(uint32_t texName, const Image& image) {
-    ATRACE_CALL();
-    const GLImage& glImage = static_cast<const GLImage&>(image);
-    const GLenum target = GL_TEXTURE_EXTERNAL_OES;
-
-    glBindTexture(target, texName);
-    if (glImage.getEGLImage() != EGL_NO_IMAGE_KHR) {
-        glEGLImageTargetTexture2DOES(target, static_cast<GLeglImageOES>(glImage.getEGLImage()));
-    }
-}
-
-void GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
-                                                 const sp<Fence>& bufferFence) {
-    ATRACE_CALL();
-
-    bool found = false;
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-        auto cachedImage = mImageCache.find(buffer->getId());
-        found = (cachedImage != mImageCache.end());
-    }
-
-    // If we couldn't find the image in the cache at this time, then either
-    // SurfaceFlinger messed up registering the buffer ahead of time or we got
-    // backed up creating other EGLImages.
-    if (!found) {
-        status_t cacheResult = mImageManager->cache(buffer);
-        if (cacheResult != NO_ERROR) {
-            ALOGE("Error with caching buffer: %d", cacheResult);
-            return;
-        }
-    }
-
-    // Whether or not we needed to cache, re-check mImageCache to make sure that
-    // there's an EGLImage. The current threading model guarantees that we don't
-    // destroy a cached image until it's really not needed anymore (i.e. this
-    // function should not be called), so the only possibility is that something
-    // terrible went wrong and we should just bind something and move on.
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-        auto cachedImage = mImageCache.find(buffer->getId());
-
-        if (cachedImage == mImageCache.end()) {
-            // We failed creating the image if we got here, so bail out.
-            ALOGE("Failed to create an EGLImage when rendering");
-            bindExternalTextureImage(texName, *createImage());
-            return;
-        }
-
-        bindExternalTextureImage(texName, *cachedImage->second);
-        mTextureView.insert_or_assign(texName, buffer->getId());
-    }
-
-    // Wait for the new buffer to be ready.
-    if (bufferFence != nullptr && bufferFence->isValid()) {
-        if (GLExtensions::getInstance().hasWaitSync()) {
-            base::unique_fd fenceFd(bufferFence->dup());
-            if (fenceFd == -1) {
-                ALOGE("error dup'ing fence fd: %d", errno);
-                return;
-            }
-            if (!waitFence(std::move(fenceFd))) {
-                ALOGE("failed to wait on fence fd");
-                return;
-            }
-        } else {
-            status_t err = bufferFence->waitForever("RenderEngine::bindExternalTextureBuffer");
-            if (err != NO_ERROR) {
-                ALOGE("error waiting for fence: %d", err);
-                return;
-            }
-        }
-    }
-
-    return;
-}
-
-void GLESRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
-                                                bool /*isRenderable*/) {
-    ATRACE_CALL();
-    mImageManager->cacheAsync(buffer, nullptr);
-}
-
-std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::cacheExternalTextureBufferForTesting(
-        const sp<GraphicBuffer>& buffer) {
-    auto barrier = std::make_shared<ImageManager::Barrier>();
-    mImageManager->cacheAsync(buffer, barrier);
-    return barrier;
-}
-
-status_t GLESRenderEngine::cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer) {
-    if (buffer == nullptr) {
-        return BAD_VALUE;
-    }
-
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-        if (mImageCache.count(buffer->getId()) > 0) {
-            // If there's already an image then fail fast here.
-            return NO_ERROR;
-        }
-    }
-    ATRACE_CALL();
-
-    // Create the image without holding a lock so that we don't block anything.
-    std::unique_ptr<Image> newImage = createImage();
-
-    bool created = newImage->setNativeWindowBuffer(buffer->getNativeBuffer(),
-                                                   buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
-    if (!created) {
-        ALOGE("Failed to create image. id=%" PRIx64 " size=%ux%u st=%u usage=%#" PRIx64 " fmt=%d",
-              buffer->getId(), buffer->getWidth(), buffer->getHeight(), buffer->getStride(),
-              buffer->getUsage(), buffer->getPixelFormat());
-        return NO_INIT;
-    }
-
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-        if (mImageCache.count(buffer->getId()) > 0) {
-            // In theory it's possible for another thread to recache the image,
-            // so bail out if another thread won.
-            return NO_ERROR;
-        }
-        mImageCache.insert(std::make_pair(buffer->getId(), std::move(newImage)));
-    }
-
-    return NO_ERROR;
-}
-
-void GLESRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
-    mImageManager->releaseAsync(buffer->getId(), nullptr);
-}
-
-std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::unbindExternalTextureBufferForTesting(
-        uint64_t bufferId) {
-    auto barrier = std::make_shared<ImageManager::Barrier>();
-    mImageManager->releaseAsync(bufferId, barrier);
-    return barrier;
-}
-
-void GLESRenderEngine::unbindExternalTextureBufferInternal(uint64_t bufferId) {
-    std::unique_ptr<Image> image;
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-        const auto& cachedImage = mImageCache.find(bufferId);
-
-        if (cachedImage != mImageCache.end()) {
-            ALOGV("Destroying image for buffer: %" PRIu64, bufferId);
-            // Move the buffer out of cache first, so that we can destroy
-            // without holding the cache's lock.
-            image = std::move(cachedImage->second);
-            mImageCache.erase(bufferId);
-            return;
-        }
-    }
-    ALOGV("Failed to find image for buffer: %" PRIu64, bufferId);
-}
-
-int GLESRenderEngine::getContextPriority() {
-    int value;
-    eglQueryContext(mEGLDisplay, mEGLContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value);
-    return value;
-}
-
-FloatRect GLESRenderEngine::setupLayerCropping(const LayerSettings& layer, Mesh& mesh) {
-    // Translate win by the rounded corners rect coordinates, to have all values in
-    // layer coordinate space.
-    FloatRect cropWin = layer.geometry.boundaries;
-    const FloatRect& roundedCornersCrop = layer.geometry.roundedCornersCrop;
-    cropWin.left -= roundedCornersCrop.left;
-    cropWin.right -= roundedCornersCrop.left;
-    cropWin.top -= roundedCornersCrop.top;
-    cropWin.bottom -= roundedCornersCrop.top;
-    Mesh::VertexArray<vec2> cropCoords(mesh.getCropCoordArray<vec2>());
-    cropCoords[0] = vec2(cropWin.left, cropWin.top);
-    cropCoords[1] = vec2(cropWin.left, cropWin.top + cropWin.getHeight());
-    cropCoords[2] = vec2(cropWin.right, cropWin.top + cropWin.getHeight());
-    cropCoords[3] = vec2(cropWin.right, cropWin.top);
-
-    setupCornerRadiusCropSize(roundedCornersCrop.getWidth(), roundedCornersCrop.getHeight());
-    return cropWin;
-}
-
-void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display,
-                                            const LayerSettings& layer, const Mesh& mesh) {
-    // We separate the layer into 3 parts essentially, such that we only turn on blending for the
-    // top rectangle and the bottom rectangle, and turn off blending for the middle rectangle.
-    FloatRect bounds = layer.geometry.roundedCornersCrop;
-
-    // Explicitly compute the transform from the clip rectangle to the physical
-    // display. Normally, this is done in glViewport but we explicitly compute
-    // it here so that we can get the scissor bounds correct.
-    const Rect& source = display.clip;
-    const Rect& destination = display.physicalDisplay;
-    // Here we compute the following transform:
-    // 1. Translate the top left corner of the source clip to (0, 0)
-    // 2. Rotate the clip rectangle about the origin in accordance with the
-    // orientation flag
-    // 3. Translate the top left corner back to the origin.
-    // 4. Scale the clip rectangle to the destination rectangle dimensions
-    // 5. Translate the top left corner to the destination rectangle's top left
-    // corner.
-    const mat4 translateSource = mat4::translate(vec4(-source.left, -source.top, 0, 1));
-    mat4 rotation;
-    int displacementX = 0;
-    int displacementY = 0;
-    float destinationWidth = static_cast<float>(destination.getWidth());
-    float destinationHeight = static_cast<float>(destination.getHeight());
-    float sourceWidth = static_cast<float>(source.getWidth());
-    float sourceHeight = static_cast<float>(source.getHeight());
-    const float rot90InRadians = 2.0f * static_cast<float>(M_PI) / 4.0f;
-    switch (display.orientation) {
-        case ui::Transform::ROT_90:
-            rotation = mat4::rotate(rot90InRadians, vec3(0, 0, 1));
-            displacementX = source.getHeight();
-            std::swap(sourceHeight, sourceWidth);
-            break;
-        case ui::Transform::ROT_180:
-            rotation = mat4::rotate(rot90InRadians * 2.0f, vec3(0, 0, 1));
-            displacementY = source.getHeight();
-            displacementX = source.getWidth();
-            break;
-        case ui::Transform::ROT_270:
-            rotation = mat4::rotate(rot90InRadians * 3.0f, vec3(0, 0, 1));
-            displacementY = source.getWidth();
-            std::swap(sourceHeight, sourceWidth);
-            break;
-        default:
-            break;
-    }
-
-    const mat4 intermediateTranslation = mat4::translate(vec4(displacementX, displacementY, 0, 1));
-    const mat4 scale = mat4::scale(
-            vec4(destinationWidth / sourceWidth, destinationHeight / sourceHeight, 1, 1));
-    const mat4 translateDestination =
-            mat4::translate(vec4(destination.left, destination.top, 0, 1));
-    const mat4 globalTransform =
-            translateDestination * scale * intermediateTranslation * rotation * translateSource;
-
-    const mat4 transformMatrix = globalTransform * layer.geometry.positionTransform;
-    const vec4 leftTopCoordinate(bounds.left, bounds.top, 1.0, 1.0);
-    const vec4 rightBottomCoordinate(bounds.right, bounds.bottom, 1.0, 1.0);
-    const vec4 leftTopCoordinateInBuffer = transformMatrix * leftTopCoordinate;
-    const vec4 rightBottomCoordinateInBuffer = transformMatrix * rightBottomCoordinate;
-    bounds = FloatRect(std::min(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
-                       std::min(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]),
-                       std::max(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
-                       std::max(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]));
-
-    // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners
-    // and the middle part without rounded corners.
-    const int32_t radius = ceil(
-            (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / 2.0);
-    const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius);
-    setScissor(topRect);
-    drawMesh(mesh);
-    const Rect bottomRect(bounds.left, bounds.bottom - radius, bounds.right, bounds.bottom);
-    setScissor(bottomRect);
-    drawMesh(mesh);
-
-    // The middle part of the layer can turn off blending.
-    if (topRect.bottom < bottomRect.top) {
-        const Rect middleRect(bounds.left, bounds.top + radius, bounds.right,
-                              bounds.bottom - radius);
-        setScissor(middleRect);
-        mState.cornerRadius = 0.0;
-        disableBlending();
-        drawMesh(mesh);
-    }
-    disableScissor();
-}
-
-status_t GLESRenderEngine::bindFrameBuffer(Framebuffer* framebuffer) {
-    ATRACE_CALL();
-    GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(framebuffer);
-    EGLImageKHR eglImage = glFramebuffer->getEGLImage();
-    uint32_t textureName = glFramebuffer->getTextureName();
-    uint32_t framebufferName = glFramebuffer->getFramebufferName();
-
-    // Bind the texture and turn our EGLImage into a texture
-    glBindTexture(GL_TEXTURE_2D, textureName);
-    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)eglImage);
-
-    // Bind the Framebuffer to render into
-    glBindFramebuffer(GL_FRAMEBUFFER, framebufferName);
-    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureName, 0);
-
-    uint32_t glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-    ALOGE_IF(glStatus != GL_FRAMEBUFFER_COMPLETE_OES, "glCheckFramebufferStatusOES error %d",
-             glStatus);
-
-    return glStatus == GL_FRAMEBUFFER_COMPLETE_OES ? NO_ERROR : BAD_VALUE;
-}
-
-void GLESRenderEngine::unbindFrameBuffer(Framebuffer* /*framebuffer*/) {
-    ATRACE_CALL();
-
-    // back to main framebuffer
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-bool GLESRenderEngine::canSkipPostRenderCleanup() const {
-    return mPriorResourcesCleaned ||
-            (mLastDrawFence != nullptr && mLastDrawFence->getStatus() != Fence::Status::Signaled);
-}
-
-void GLESRenderEngine::cleanupPostRender() {
-    ATRACE_CALL();
-
-    if (canSkipPostRenderCleanup()) {
-        // If we don't have a prior frame needing cleanup, then don't do anything.
-        return;
-    }
-
-    // Bind the texture to placeholder so that backing image data can be freed.
-    GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(getFramebufferForDrawing());
-    glFramebuffer->allocateBuffers(1, 1, mPlaceholderDrawBuffer);
-
-    // Release the cached fence here, so that we don't churn reallocations when
-    // we could no-op repeated calls of this method instead.
-    mLastDrawFence = nullptr;
-    mPriorResourcesCleaned = true;
-}
-
-void GLESRenderEngine::cleanFramebufferCache() {
-    std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
-    // Bind the texture to placeholder so that backing image data can be freed.
-    GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(getFramebufferForDrawing());
-    glFramebuffer->allocateBuffers(1, 1, mPlaceholderDrawBuffer);
-
-    while (!mFramebufferImageCache.empty()) {
-        EGLImageKHR expired = mFramebufferImageCache.front().second;
-        mFramebufferImageCache.pop_front();
-        eglDestroyImageKHR(mEGLDisplay, expired);
-        DEBUG_EGL_IMAGE_TRACKER_DESTROY();
-    }
-}
-
-void GLESRenderEngine::checkErrors() const {
-    checkErrors(nullptr);
-}
-
-void GLESRenderEngine::checkErrors(const char* tag) const {
-    do {
-        // there could be more than one error flag
-        GLenum error = glGetError();
-        if (error == GL_NO_ERROR) break;
-        if (tag == nullptr) {
-            ALOGE("GL error 0x%04x", int(error));
-        } else {
-            ALOGE("GL error: %s -> 0x%04x", tag, int(error));
-        }
-    } while (true);
-}
-
-bool GLESRenderEngine::supportsProtectedContent() const {
-    return mProtectedEGLContext != EGL_NO_CONTEXT;
-}
-
-void GLESRenderEngine::useProtectedContext(bool useProtectedContext) {
-    if (useProtectedContext == mInProtectedContext ||
-        (useProtectedContext && !supportsProtectedContent())) {
-        return;
-    }
-
-    const EGLSurface surface = useProtectedContext ? mProtectedStubSurface : mStubSurface;
-    const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext;
-    if (eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE) {
-        mInProtectedContext = useProtectedContext;
-    }
-}
-EGLImageKHR GLESRenderEngine::createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer,
-                                                             bool isProtected,
-                                                             bool useFramebufferCache) {
-    sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(nativeBuffer);
-    if (useFramebufferCache) {
-        std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
-        for (const auto& image : mFramebufferImageCache) {
-            if (image.first == graphicBuffer->getId()) {
-                return image.second;
-            }
-        }
-    }
-    EGLint attributes[] = {
-            isProtected ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
-            isProtected ? EGL_TRUE : EGL_NONE,
-            EGL_NONE,
-    };
-    EGLImageKHR image = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
-                                          nativeBuffer, attributes);
-    if (useFramebufferCache) {
-        if (image != EGL_NO_IMAGE_KHR) {
-            std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
-            if (mFramebufferImageCache.size() >= mFramebufferImageCacheSize) {
-                EGLImageKHR expired = mFramebufferImageCache.front().second;
-                mFramebufferImageCache.pop_front();
-                eglDestroyImageKHR(mEGLDisplay, expired);
-                DEBUG_EGL_IMAGE_TRACKER_DESTROY();
-            }
-            mFramebufferImageCache.push_back({graphicBuffer->getId(), image});
-        }
-    }
-
-    if (image != EGL_NO_IMAGE_KHR) {
-        DEBUG_EGL_IMAGE_TRACKER_CREATE();
-    }
-    return image;
-}
-
-void GLESRenderEngine::drawLayersInternal(
-        const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
-        base::unique_fd&& bufferFence) {
-    ATRACE_CALL();
-    if (layers.empty()) {
-        ALOGV("Drawing empty layer stack");
-        resultPromise->set_value(Fence::NO_FENCE);
-        return;
-    }
-
-    if (bufferFence.get() >= 0) {
-        // Duplicate the fence for passing to waitFence.
-        base::unique_fd bufferFenceDup(dup(bufferFence.get()));
-        if (bufferFenceDup < 0 || !waitFence(std::move(bufferFenceDup))) {
-            ATRACE_NAME("Waiting before draw");
-            sync_wait(bufferFence.get(), -1);
-        }
-    }
-
-    if (buffer == nullptr) {
-        ALOGE("No output buffer provided. Aborting GPU composition.");
-        resultPromise->set_value(base::unexpected(BAD_VALUE));
-        return;
-    }
-
-    validateOutputBufferUsage(buffer->getBuffer());
-
-    std::unique_ptr<BindNativeBufferAsFramebuffer> fbo;
-    // Gathering layers that requested blur, we'll need them to decide when to render to an
-    // offscreen buffer, and when to render to the native buffer.
-    std::deque<const LayerSettings> blurLayers;
-    if (CC_LIKELY(mBlurFilter != nullptr)) {
-        for (const auto& layer : layers) {
-            if (layer.backgroundBlurRadius > 0) {
-                blurLayers.push_back(layer);
-            }
-        }
-    }
-    const auto blurLayersSize = blurLayers.size();
-
-    if (blurLayersSize == 0) {
-        fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
-                                                              buffer->getBuffer()
-                                                                      .get()
-                                                                      ->getNativeBuffer(),
-                                                              useFramebufferCache);
-        if (fbo->getStatus() != NO_ERROR) {
-            ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
-                  buffer->getBuffer()->handle);
-            checkErrors();
-            resultPromise->set_value(base::unexpected(fbo->getStatus()));
-            return;
-        }
-        setViewportAndProjection(display.physicalDisplay, display.clip);
-    } else {
-        setViewportAndProjection(display.physicalDisplay, display.clip);
-        auto status =
-                mBlurFilter->setAsDrawTarget(display, blurLayers.front().backgroundBlurRadius);
-        if (status != NO_ERROR) {
-            ALOGE("Failed to prepare blur filter! Aborting GPU composition for buffer (%p).",
-                  buffer->getBuffer()->handle);
-            checkErrors();
-            resultPromise->set_value(base::unexpected(status));
-            return;
-        }
-    }
-
-    // clear the entire buffer, sometimes when we reuse buffers we'd persist
-    // ghost images otherwise.
-    // we also require a full transparent framebuffer for overlays. This is
-    // probably not quite efficient on all GPUs, since we could filter out
-    // opaque layers.
-    clearWithColor(0.0, 0.0, 0.0, 0.0);
-
-    setOutputDataSpace(display.outputDataspace);
-    setDisplayMaxLuminance(display.maxLuminance);
-    setDisplayColorTransform(display.colorTransform);
-
-    const mat4 projectionMatrix =
-            ui::Transform(display.orientation).asMatrix4() * mState.projectionMatrix;
-
-    Mesh mesh = Mesh::Builder()
-                        .setPrimitive(Mesh::TRIANGLE_FAN)
-                        .setVertices(4 /* count */, 2 /* size */)
-                        .setTexCoords(2 /* size */)
-                        .setCropCoords(2 /* size */)
-                        .build();
-    for (const auto& layer : layers) {
-        if (blurLayers.size() > 0 && blurLayers.front() == layer) {
-            blurLayers.pop_front();
-
-            auto status = mBlurFilter->prepare();
-            if (status != NO_ERROR) {
-                ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
-                      buffer->getBuffer()->handle);
-                checkErrors("Can't render first blur pass");
-                resultPromise->set_value(base::unexpected(status));
-                return;
-            }
-
-            if (blurLayers.size() == 0) {
-                // Done blurring, time to bind the native FBO and render our blur onto it.
-                fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
-                                                                      buffer.get()
-                                                                              ->getBuffer()
-                                                                              ->getNativeBuffer(),
-                                                                      useFramebufferCache);
-                status = fbo->getStatus();
-                setViewportAndProjection(display.physicalDisplay, display.clip);
-            } else {
-                // There's still something else to blur, so let's keep rendering to our FBO
-                // instead of to the display.
-                status = mBlurFilter->setAsDrawTarget(display,
-                                                      blurLayers.front().backgroundBlurRadius);
-            }
-            if (status != NO_ERROR) {
-                ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
-                      buffer->getBuffer()->handle);
-                checkErrors("Can't bind native framebuffer");
-                resultPromise->set_value(base::unexpected(status));
-                return;
-            }
-
-            status = mBlurFilter->render(blurLayersSize > 1);
-            if (status != NO_ERROR) {
-                ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
-                      buffer->getBuffer()->handle);
-                checkErrors("Can't render blur filter");
-                resultPromise->set_value(base::unexpected(status));
-                return;
-            }
-        }
-
-        // Ensure luminance is at least 100 nits to avoid div-by-zero
-        const float maxLuminance = std::max(100.f, layer.source.buffer.maxLuminanceNits);
-        mState.maxMasteringLuminance = maxLuminance;
-        mState.maxContentLuminance = maxLuminance;
-        mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
-
-        const FloatRect bounds = layer.geometry.boundaries;
-        Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
-        position[0] = vec2(bounds.left, bounds.top);
-        position[1] = vec2(bounds.left, bounds.bottom);
-        position[2] = vec2(bounds.right, bounds.bottom);
-        position[3] = vec2(bounds.right, bounds.top);
-
-        setupLayerCropping(layer, mesh);
-        setColorTransform(layer.colorTransform);
-
-        bool usePremultipliedAlpha = true;
-        bool disableTexture = true;
-        bool isOpaque = false;
-        if (layer.source.buffer.buffer != nullptr) {
-            disableTexture = false;
-            isOpaque = layer.source.buffer.isOpaque;
-
-            sp<GraphicBuffer> gBuf = layer.source.buffer.buffer->getBuffer();
-            validateInputBufferUsage(gBuf);
-            bindExternalTextureBuffer(layer.source.buffer.textureName, gBuf,
-                                      layer.source.buffer.fence);
-
-            usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
-            Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
-            mat4 texMatrix = layer.source.buffer.textureTransform;
-
-            texture.setMatrix(texMatrix.asArray());
-            texture.setFiltering(layer.source.buffer.useTextureFiltering);
-
-            texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
-            setSourceY410BT2020(layer.source.buffer.isY410BT2020);
-
-            renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
-            texCoords[0] = vec2(0.0, 0.0);
-            texCoords[1] = vec2(0.0, 1.0);
-            texCoords[2] = vec2(1.0, 1.0);
-            texCoords[3] = vec2(1.0, 0.0);
-            setupLayerTexturing(texture);
-
-            // Do not cache protected EGLImage, protected memory is limited.
-            if (gBuf->getUsage() & GRALLOC_USAGE_PROTECTED) {
-                unmapExternalTextureBuffer(std::move(gBuf));
-            }
-        }
-
-        const half3 solidColor = layer.source.solidColor;
-        const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
-        const float radius =
-                (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) /
-                2.0f;
-        // Buffer sources will have a black solid color ignored in the shader,
-        // so in that scenario the solid color passed here is arbitrary.
-        setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, radius);
-        if (layer.disableBlending) {
-            glDisable(GL_BLEND);
-        }
-        setSourceDataSpace(layer.sourceDataspace);
-
-        if (layer.shadow.length > 0.0f) {
-            handleShadow(layer.geometry.boundaries, radius, layer.shadow);
-        }
-        // We only want to do a special handling for rounded corners when having rounded corners
-        // is the only reason it needs to turn on blending, otherwise, we handle it like the
-        // usual way since it needs to turn on blending anyway.
-        else if (radius > 0.0 && color.a >= 1.0f && isOpaque) {
-            handleRoundedCorners(display, layer, mesh);
-        } else {
-            drawMesh(mesh);
-        }
-
-        // Cleanup if there's a buffer source
-        if (layer.source.buffer.buffer != nullptr) {
-            disableBlending();
-            setSourceY410BT2020(false);
-            disableTexturing();
-        }
-    }
-
-    base::unique_fd drawFence = flush();
-
-    // If flush failed or we don't support native fences, we need to force the
-    // gl command stream to be executed.
-    if (drawFence.get() < 0) {
-        bool success = finish();
-        if (!success) {
-            ALOGE("Failed to flush RenderEngine commands");
-            checkErrors();
-            // Chances are, something illegal happened (either the caller passed
-            // us bad parameters, or we messed up our shader generation).
-            resultPromise->set_value(base::unexpected(INVALID_OPERATION));
-            return;
-        }
-        mLastDrawFence = nullptr;
-    } else {
-        // The caller takes ownership of drawFence, so we need to duplicate the
-        // fd here.
-        mLastDrawFence = new Fence(dup(drawFence.get()));
-    }
-    mPriorResourcesCleaned = false;
-
-    checkErrors();
-    resultPromise->set_value(sp<Fence>::make(std::move(drawFence)));
-}
-
-void GLESRenderEngine::setViewportAndProjection(Rect viewport, Rect clip) {
-    ATRACE_CALL();
-    mVpWidth = viewport.getWidth();
-    mVpHeight = viewport.getHeight();
-
-    // We pass the the top left corner instead of the bottom left corner,
-    // because since we're rendering off-screen first.
-    glViewport(viewport.left, viewport.top, mVpWidth, mVpHeight);
-
-    mState.projectionMatrix = mat4::ortho(clip.left, clip.right, clip.top, clip.bottom, 0, 1);
-}
-
-void GLESRenderEngine::setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
-                                          const half4& color, float cornerRadius) {
-    mState.isPremultipliedAlpha = premultipliedAlpha;
-    mState.isOpaque = opaque;
-    mState.color = color;
-    mState.cornerRadius = cornerRadius;
-
-    if (disableTexture) {
-        mState.textureEnabled = false;
-    }
-
-    if (color.a < 1.0f || !opaque || cornerRadius > 0.0f) {
-        glEnable(GL_BLEND);
-        glBlendFuncSeparate(premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
-                            GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-    } else {
-        glDisable(GL_BLEND);
-    }
-}
-
-void GLESRenderEngine::setSourceY410BT2020(bool enable) {
-    mState.isY410BT2020 = enable;
-}
-
-void GLESRenderEngine::setSourceDataSpace(Dataspace source) {
-    mDataSpace = source;
-}
-
-void GLESRenderEngine::setOutputDataSpace(Dataspace dataspace) {
-    mOutputDataSpace = dataspace;
-}
-
-void GLESRenderEngine::setDisplayMaxLuminance(const float maxLuminance) {
-    mState.displayMaxLuminance = maxLuminance;
-}
-
-void GLESRenderEngine::setupLayerTexturing(const Texture& texture) {
-    GLuint target = texture.getTextureTarget();
-    glBindTexture(target, texture.getTextureName());
-    GLenum filter = GL_NEAREST;
-    if (texture.getFiltering()) {
-        filter = GL_LINEAR;
-    }
-    glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
-    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
-
-    mState.texture = texture;
-    mState.textureEnabled = true;
-}
-
-void GLESRenderEngine::setColorTransform(const mat4& colorTransform) {
-    mState.colorMatrix = colorTransform;
-}
-
-void GLESRenderEngine::setDisplayColorTransform(const mat4& colorTransform) {
-    mState.displayColorMatrix = colorTransform;
-}
-
-void GLESRenderEngine::disableTexturing() {
-    mState.textureEnabled = false;
-}
-
-void GLESRenderEngine::disableBlending() {
-    glDisable(GL_BLEND);
-}
-
-void GLESRenderEngine::setupFillWithColor(float r, float g, float b, float a) {
-    mState.isPremultipliedAlpha = true;
-    mState.isOpaque = false;
-    mState.color = half4(r, g, b, a);
-    mState.textureEnabled = false;
-    glDisable(GL_BLEND);
-}
-
-void GLESRenderEngine::setupCornerRadiusCropSize(float width, float height) {
-    mState.cropSize = half2(width, height);
-}
-
-void GLESRenderEngine::drawMesh(const Mesh& mesh) {
-    ATRACE_CALL();
-    if (mesh.getTexCoordsSize()) {
-        glEnableVertexAttribArray(Program::texCoords);
-        glVertexAttribPointer(Program::texCoords, mesh.getTexCoordsSize(), GL_FLOAT, GL_FALSE,
-                              mesh.getByteStride(), mesh.getTexCoords());
-    }
-
-    glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
-                          mesh.getByteStride(), mesh.getPositions());
-
-    if (mState.cornerRadius > 0.0f) {
-        glEnableVertexAttribArray(Program::cropCoords);
-        glVertexAttribPointer(Program::cropCoords, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
-                              mesh.getByteStride(), mesh.getCropCoords());
-    }
-
-    if (mState.drawShadows) {
-        glEnableVertexAttribArray(Program::shadowColor);
-        glVertexAttribPointer(Program::shadowColor, mesh.getShadowColorSize(), GL_FLOAT, GL_FALSE,
-                              mesh.getByteStride(), mesh.getShadowColor());
-
-        glEnableVertexAttribArray(Program::shadowParams);
-        glVertexAttribPointer(Program::shadowParams, mesh.getShadowParamsSize(), GL_FLOAT, GL_FALSE,
-                              mesh.getByteStride(), mesh.getShadowParams());
-    }
-
-    Description managedState = mState;
-    // By default, DISPLAY_P3 is the only supported wide color output. However,
-    // when HDR content is present, hardware composer may be able to handle
-    // BT2020 data space, in that case, the output data space is set to be
-    // BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
-    // to respect this and convert non-HDR content to HDR format.
-    if (mUseColorManagement) {
-        Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
-        Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
-        Dataspace outputStandard =
-                static_cast<Dataspace>(mOutputDataSpace & Dataspace::STANDARD_MASK);
-        Dataspace outputTransfer =
-                static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
-        bool needsXYZConversion = needsXYZTransformMatrix();
-
-        // NOTE: if the input standard of the input dataspace is not STANDARD_DCI_P3 or
-        // STANDARD_BT2020, it will be  treated as STANDARD_BT709
-        if (inputStandard != Dataspace::STANDARD_DCI_P3 &&
-            inputStandard != Dataspace::STANDARD_BT2020) {
-            inputStandard = Dataspace::STANDARD_BT709;
-        }
-
-        if (needsXYZConversion) {
-            // The supported input color spaces are standard RGB, Display P3 and BT2020.
-            switch (inputStandard) {
-                case Dataspace::STANDARD_DCI_P3:
-                    managedState.inputTransformMatrix = mDisplayP3ToXyz;
-                    break;
-                case Dataspace::STANDARD_BT2020:
-                    managedState.inputTransformMatrix = mBt2020ToXyz;
-                    break;
-                default:
-                    managedState.inputTransformMatrix = mSrgbToXyz;
-                    break;
-            }
-
-            // The supported output color spaces are BT2020, Display P3 and standard RGB.
-            switch (outputStandard) {
-                case Dataspace::STANDARD_BT2020:
-                    managedState.outputTransformMatrix = mXyzToBt2020;
-                    break;
-                case Dataspace::STANDARD_DCI_P3:
-                    managedState.outputTransformMatrix = mXyzToDisplayP3;
-                    break;
-                default:
-                    managedState.outputTransformMatrix = mXyzToSrgb;
-                    break;
-            }
-        } else if (inputStandard != outputStandard) {
-            // At this point, the input data space and output data space could be both
-            // HDR data spaces, but they match each other, we do nothing in this case.
-            // In addition to the case above, the input data space could be
-            // - scRGB linear
-            // - scRGB non-linear
-            // - sRGB
-            // - Display P3
-            // - BT2020
-            // The output data spaces could be
-            // - sRGB
-            // - Display P3
-            // - BT2020
-            switch (outputStandard) {
-                case Dataspace::STANDARD_BT2020:
-                    if (inputStandard == Dataspace::STANDARD_BT709) {
-                        managedState.outputTransformMatrix = mSrgbToBt2020;
-                    } else if (inputStandard == Dataspace::STANDARD_DCI_P3) {
-                        managedState.outputTransformMatrix = mDisplayP3ToBt2020;
-                    }
-                    break;
-                case Dataspace::STANDARD_DCI_P3:
-                    if (inputStandard == Dataspace::STANDARD_BT709) {
-                        managedState.outputTransformMatrix = mSrgbToDisplayP3;
-                    } else if (inputStandard == Dataspace::STANDARD_BT2020) {
-                        managedState.outputTransformMatrix = mBt2020ToDisplayP3;
-                    }
-                    break;
-                default:
-                    if (inputStandard == Dataspace::STANDARD_DCI_P3) {
-                        managedState.outputTransformMatrix = mDisplayP3ToSrgb;
-                    } else if (inputStandard == Dataspace::STANDARD_BT2020) {
-                        managedState.outputTransformMatrix = mBt2020ToSrgb;
-                    }
-                    break;
-            }
-        }
-
-        // we need to convert the RGB value to linear space and convert it back when:
-        // - there is a color matrix that is not an identity matrix, or
-        // - there is an output transform matrix that is not an identity matrix, or
-        // - the input transfer function doesn't match the output transfer function.
-        if (managedState.hasColorMatrix() || managedState.hasOutputTransformMatrix() ||
-            inputTransfer != outputTransfer) {
-            managedState.inputTransferFunction =
-                    Description::dataSpaceToTransferFunction(inputTransfer);
-            managedState.outputTransferFunction =
-                    Description::dataSpaceToTransferFunction(outputTransfer);
-        }
-    }
-
-    ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
-                                           managedState);
-
-    if (mState.drawShadows) {
-        glDrawElements(mesh.getPrimitive(), mesh.getIndexCount(), GL_UNSIGNED_SHORT,
-                       mesh.getIndices());
-    } else {
-        glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
-    }
-
-    if (mUseColorManagement && outputDebugPPMs) {
-        static uint64_t managedColorFrameCount = 0;
-        std::ostringstream out;
-        out << "/data/texture_out" << managedColorFrameCount++;
-        writePPM(out.str().c_str(), mVpWidth, mVpHeight);
-    }
-
-    if (mesh.getTexCoordsSize()) {
-        glDisableVertexAttribArray(Program::texCoords);
-    }
-
-    if (mState.cornerRadius > 0.0f) {
-        glDisableVertexAttribArray(Program::cropCoords);
-    }
-
-    if (mState.drawShadows) {
-        glDisableVertexAttribArray(Program::shadowColor);
-        glDisableVertexAttribArray(Program::shadowParams);
-    }
-}
-
-size_t GLESRenderEngine::getMaxTextureSize() const {
-    return mMaxTextureSize;
-}
-
-size_t GLESRenderEngine::getMaxViewportDims() const {
-    return mMaxViewportDims[0] < mMaxViewportDims[1] ? mMaxViewportDims[0] : mMaxViewportDims[1];
-}
-
-void GLESRenderEngine::dump(std::string& result) {
-    const GLExtensions& extensions = GLExtensions::getInstance();
-    ProgramCache& cache = ProgramCache::getInstance();
-
-    StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion());
-    StringAppendF(&result, "%s\n", extensions.getEGLExtensions());
-    StringAppendF(&result, "GLES: %s, %s, %s\n", extensions.getVendor(), extensions.getRenderer(),
-                  extensions.getVersion());
-    StringAppendF(&result, "%s\n", extensions.getExtensions());
-    StringAppendF(&result, "RenderEngine supports protected context: %d\n",
-                  supportsProtectedContent());
-    StringAppendF(&result, "RenderEngine is in protected context: %d\n", mInProtectedContext);
-    StringAppendF(&result, "RenderEngine program cache size for unprotected context: %zu\n",
-                  cache.getSize(mEGLContext));
-    StringAppendF(&result, "RenderEngine program cache size for protected context: %zu\n",
-                  cache.getSize(mProtectedEGLContext));
-    StringAppendF(&result, "RenderEngine last dataspace conversion: (%s) to (%s)\n",
-                  dataspaceDetails(static_cast<android_dataspace>(mDataSpace)).c_str(),
-                  dataspaceDetails(static_cast<android_dataspace>(mOutputDataSpace)).c_str());
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-        StringAppendF(&result, "RenderEngine image cache size: %zu\n", mImageCache.size());
-        StringAppendF(&result, "Dumping buffer ids...\n");
-        for (const auto& [id, unused] : mImageCache) {
-            StringAppendF(&result, "0x%" PRIx64 "\n", id);
-        }
-    }
-    {
-        std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
-        StringAppendF(&result, "RenderEngine framebuffer image cache size: %zu\n",
-                      mFramebufferImageCache.size());
-        StringAppendF(&result, "Dumping buffer ids...\n");
-        for (const auto& [id, unused] : mFramebufferImageCache) {
-            StringAppendF(&result, "0x%" PRIx64 "\n", id);
-        }
-    }
-}
-
-GLESRenderEngine::GlesVersion GLESRenderEngine::parseGlesVersion(const char* str) {
-    int major, minor;
-    if (sscanf(str, "OpenGL ES-CM %d.%d", &major, &minor) != 2) {
-        if (sscanf(str, "OpenGL ES %d.%d", &major, &minor) != 2) {
-            ALOGW("Unable to parse GL_VERSION string: \"%s\"", str);
-            return GLES_VERSION_1_0;
-        }
-    }
-
-    if (major == 1 && minor == 0) return GLES_VERSION_1_0;
-    if (major == 1 && minor >= 1) return GLES_VERSION_1_1;
-    if (major == 2 && minor >= 0) return GLES_VERSION_2_0;
-    if (major == 3 && minor >= 0) return GLES_VERSION_3_0;
-
-    ALOGW("Unrecognized OpenGL ES version: %d.%d", major, minor);
-    return GLES_VERSION_1_0;
-}
-
-EGLContext GLESRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
-                                              EGLContext shareContext,
-                                              std::optional<ContextPriority> contextPriority,
-                                              Protection protection) {
-    EGLint renderableType = 0;
-    if (config == EGL_NO_CONFIG) {
-        renderableType = EGL_OPENGL_ES3_BIT;
-    } else if (!eglGetConfigAttrib(display, config, EGL_RENDERABLE_TYPE, &renderableType)) {
-        LOG_ALWAYS_FATAL("can't query EGLConfig RENDERABLE_TYPE");
-    }
-    EGLint contextClientVersion = 0;
-    if (renderableType & EGL_OPENGL_ES3_BIT) {
-        contextClientVersion = 3;
-    } else if (renderableType & EGL_OPENGL_ES2_BIT) {
-        contextClientVersion = 2;
-    } else if (renderableType & EGL_OPENGL_ES_BIT) {
-        contextClientVersion = 1;
-    } else {
-        LOG_ALWAYS_FATAL("no supported EGL_RENDERABLE_TYPEs");
-    }
-
-    std::vector<EGLint> contextAttributes;
-    contextAttributes.reserve(7);
-    contextAttributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
-    contextAttributes.push_back(contextClientVersion);
-    if (contextPriority) {
-        contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
-        switch (*contextPriority) {
-            case ContextPriority::REALTIME:
-                contextAttributes.push_back(EGL_CONTEXT_PRIORITY_REALTIME_NV);
-                break;
-            case ContextPriority::MEDIUM:
-                contextAttributes.push_back(EGL_CONTEXT_PRIORITY_MEDIUM_IMG);
-                break;
-            case ContextPriority::LOW:
-                contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LOW_IMG);
-                break;
-            case ContextPriority::HIGH:
-            default:
-                contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
-                break;
-        }
-    }
-    if (protection == Protection::PROTECTED) {
-        contextAttributes.push_back(EGL_PROTECTED_CONTENT_EXT);
-        contextAttributes.push_back(EGL_TRUE);
-    }
-    contextAttributes.push_back(EGL_NONE);
-
-    EGLContext context = eglCreateContext(display, config, shareContext, contextAttributes.data());
-
-    if (contextClientVersion == 3 && context == EGL_NO_CONTEXT) {
-        // eglGetConfigAttrib indicated we can create GLES 3 context, but we failed, thus
-        // EGL_NO_CONTEXT so that we can abort.
-        if (config != EGL_NO_CONFIG) {
-            return context;
-        }
-        // If |config| is EGL_NO_CONFIG, we speculatively try to create GLES 3 context, so we should
-        // try to fall back to GLES 2.
-        contextAttributes[1] = 2;
-        context = eglCreateContext(display, config, shareContext, contextAttributes.data());
-    }
-
-    return context;
-}
-
-EGLSurface GLESRenderEngine::createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
-                                                         int hwcFormat, Protection protection) {
-    EGLConfig stubConfig = config;
-    if (stubConfig == EGL_NO_CONFIG) {
-        stubConfig = chooseEglConfig(display, hwcFormat, /*logConfig*/ true);
-    }
-    std::vector<EGLint> attributes;
-    attributes.reserve(7);
-    attributes.push_back(EGL_WIDTH);
-    attributes.push_back(1);
-    attributes.push_back(EGL_HEIGHT);
-    attributes.push_back(1);
-    if (protection == Protection::PROTECTED) {
-        attributes.push_back(EGL_PROTECTED_CONTENT_EXT);
-        attributes.push_back(EGL_TRUE);
-    }
-    attributes.push_back(EGL_NONE);
-
-    return eglCreatePbufferSurface(display, stubConfig, attributes.data());
-}
-
-bool GLESRenderEngine::isHdrDataSpace(const Dataspace dataSpace) const {
-    const Dataspace standard = static_cast<Dataspace>(dataSpace & Dataspace::STANDARD_MASK);
-    const Dataspace transfer = static_cast<Dataspace>(dataSpace & Dataspace::TRANSFER_MASK);
-    return standard == Dataspace::STANDARD_BT2020 &&
-            (transfer == Dataspace::TRANSFER_ST2084 || transfer == Dataspace::TRANSFER_HLG);
-}
-
-// For convenience, we want to convert the input color space to XYZ color space first,
-// and then convert from XYZ color space to output color space when
-// - SDR and HDR contents are mixed, either SDR content will be converted to HDR or
-//   HDR content will be tone-mapped to SDR; Or,
-// - there are HDR PQ and HLG contents presented at the same time, where we want to convert
-//   HLG content to PQ content.
-// In either case above, we need to operate the Y value in XYZ color space. Thus, when either
-// input data space or output data space is HDR data space, and the input transfer function
-// doesn't match the output transfer function, we would enable an intermediate transfrom to
-// XYZ color space.
-bool GLESRenderEngine::needsXYZTransformMatrix() const {
-    const bool isInputHdrDataSpace = isHdrDataSpace(mDataSpace);
-    const bool isOutputHdrDataSpace = isHdrDataSpace(mOutputDataSpace);
-    const Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
-    const Dataspace outputTransfer =
-            static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
-
-    return (isInputHdrDataSpace || isOutputHdrDataSpace) && inputTransfer != outputTransfer;
-}
-
-bool GLESRenderEngine::isImageCachedForTesting(uint64_t bufferId) {
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    const auto& cachedImage = mImageCache.find(bufferId);
-    return cachedImage != mImageCache.end();
-}
-
-bool GLESRenderEngine::isTextureNameKnownForTesting(uint32_t texName) {
-    const auto& entry = mTextureView.find(texName);
-    return entry != mTextureView.end();
-}
-
-std::optional<uint64_t> GLESRenderEngine::getBufferIdForTextureNameForTesting(uint32_t texName) {
-    const auto& entry = mTextureView.find(texName);
-    return entry != mTextureView.end() ? entry->second : std::nullopt;
-}
-
-bool GLESRenderEngine::isFramebufferImageCachedForTesting(uint64_t bufferId) {
-    std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
-    return std::any_of(mFramebufferImageCache.cbegin(), mFramebufferImageCache.cend(),
-                       [=](std::pair<uint64_t, EGLImageKHR> image) {
-                           return image.first == bufferId;
-                       });
-}
-
-// FlushTracer implementation
-GLESRenderEngine::FlushTracer::FlushTracer(GLESRenderEngine* engine) : mEngine(engine) {
-    mThread = std::thread(&GLESRenderEngine::FlushTracer::loop, this);
-}
-
-GLESRenderEngine::FlushTracer::~FlushTracer() {
-    {
-        std::lock_guard<std::mutex> lock(mMutex);
-        mRunning = false;
-    }
-    mCondition.notify_all();
-    if (mThread.joinable()) {
-        mThread.join();
-    }
-}
-
-void GLESRenderEngine::FlushTracer::queueSync(EGLSyncKHR sync) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    char name[64];
-    const uint64_t frameNum = mFramesQueued++;
-    snprintf(name, sizeof(name), "Queueing sync for frame: %lu",
-             static_cast<unsigned long>(frameNum));
-    ATRACE_NAME(name);
-    mQueue.push({sync, frameNum});
-    ATRACE_INT("GPU Frames Outstanding", mQueue.size());
-    mCondition.notify_one();
-}
-
-void GLESRenderEngine::FlushTracer::loop() {
-    while (mRunning) {
-        QueueEntry entry;
-        {
-            std::lock_guard<std::mutex> lock(mMutex);
-
-            mCondition.wait(mMutex,
-                            [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
-
-            if (!mRunning) {
-                // if mRunning is false, then FlushTracer is being destroyed, so
-                // bail out now.
-                break;
-            }
-            entry = mQueue.front();
-            mQueue.pop();
-        }
-        {
-            char name[64];
-            snprintf(name, sizeof(name), "waiting for frame %lu",
-                     static_cast<unsigned long>(entry.mFrameNum));
-            ATRACE_NAME(name);
-            mEngine->waitSync(entry.mSync, 0);
-        }
-    }
-}
-
-void GLESRenderEngine::handleShadow(const FloatRect& casterRect, float casterCornerRadius,
-                                    const ShadowSettings& settings) {
-    ATRACE_CALL();
-    const float casterZ = settings.length / 2.0f;
-    const GLShadowVertexGenerator shadows(casterRect, casterCornerRadius, casterZ,
-                                          settings.casterIsTranslucent, settings.ambientColor,
-                                          settings.spotColor, settings.lightPos,
-                                          settings.lightRadius);
-
-    // setup mesh for both shadows
-    Mesh mesh = Mesh::Builder()
-                        .setPrimitive(Mesh::TRIANGLES)
-                        .setVertices(shadows.getVertexCount(), 2 /* size */)
-                        .setShadowAttrs()
-                        .setIndices(shadows.getIndexCount())
-                        .build();
-
-    Mesh::VertexArray<vec2> position = mesh.getPositionArray<vec2>();
-    Mesh::VertexArray<vec4> shadowColor = mesh.getShadowColorArray<vec4>();
-    Mesh::VertexArray<vec3> shadowParams = mesh.getShadowParamsArray<vec3>();
-    shadows.fillVertices(position, shadowColor, shadowParams);
-    shadows.fillIndices(mesh.getIndicesArray());
-
-    mState.cornerRadius = 0.0f;
-    mState.drawShadows = true;
-    setupLayerTexturing(mShadowTexture->getTexture());
-    drawMesh(mesh);
-    mState.drawShadows = false;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
deleted file mode 100644
index 402ff52..0000000
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 SF_GLESRENDERENGINE_H_
-#define SF_GLESRENDERENGINE_H_
-
-#include <condition_variable>
-#include <deque>
-#include <mutex>
-#include <queue>
-#include <thread>
-#include <unordered_map>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <android-base/thread_annotations.h>
-#include <renderengine/RenderEngine.h>
-#include <renderengine/private/Description.h>
-#include <sys/types.h>
-#include <ui/FenceResult.h>
-#include "GLShadowTexture.h"
-#include "ImageManager.h"
-
-#define EGL_NO_CONFIG ((EGLConfig)0)
-
-namespace android {
-
-namespace renderengine {
-
-class Mesh;
-class Texture;
-
-namespace gl {
-
-class GLImage;
-class BlurFilter;
-
-class GLESRenderEngine : public RenderEngine {
-public:
-    static std::unique_ptr<GLESRenderEngine> create(const RenderEngineCreationArgs& args);
-
-    GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLConfig config,
-                     EGLContext ctxt, EGLSurface stub, EGLContext protectedContext,
-                     EGLSurface protectedStub);
-    ~GLESRenderEngine() override EXCLUDES(mRenderingMutex);
-
-    std::future<void> primeCache() override;
-    void genTextures(size_t count, uint32_t* names) override;
-    void deleteTextures(size_t count, uint32_t const* names) override;
-    bool isProtected() const { return mInProtectedContext; }
-    bool supportsProtectedContent() const override;
-    void useProtectedContext(bool useProtectedContext) override;
-    void cleanupPostRender() override;
-    int getContextPriority() override;
-    bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; }
-    void onActiveDisplaySizeChanged(ui::Size size) override {}
-
-    EGLDisplay getEGLDisplay() const { return mEGLDisplay; }
-    // Creates an output image for rendering to
-    EGLImageKHR createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer, bool isProtected,
-                                               bool useFramebufferCache)
-            EXCLUDES(mFramebufferImageCacheMutex);
-
-    // Test-only methods
-    // Returns true iff mImageCache contains an image keyed by bufferId
-    bool isImageCachedForTesting(uint64_t bufferId) EXCLUDES(mRenderingMutex);
-    // Returns true iff texName was previously generated by RenderEngine and was
-    // not destroyed.
-    bool isTextureNameKnownForTesting(uint32_t texName);
-    // Returns the buffer ID of the content bound to texName, or nullopt if no
-    // such mapping exists.
-    std::optional<uint64_t> getBufferIdForTextureNameForTesting(uint32_t texName);
-    // Returns true iff mFramebufferImageCache contains an image keyed by bufferId
-    bool isFramebufferImageCachedForTesting(uint64_t bufferId)
-            EXCLUDES(mFramebufferImageCacheMutex);
-    // These are wrappers around public methods above, but exposing Barrier
-    // objects so that tests can block.
-    std::shared_ptr<ImageManager::Barrier> cacheExternalTextureBufferForTesting(
-            const sp<GraphicBuffer>& buffer);
-    std::shared_ptr<ImageManager::Barrier> unbindExternalTextureBufferForTesting(uint64_t bufferId);
-
-protected:
-    Framebuffer* getFramebufferForDrawing();
-    void dump(std::string& result) override EXCLUDES(mRenderingMutex)
-            EXCLUDES(mFramebufferImageCacheMutex);
-    size_t getMaxTextureSize() const override;
-    size_t getMaxViewportDims() const override;
-    void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable)
-            EXCLUDES(mRenderingMutex);
-    void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) EXCLUDES(mRenderingMutex);
-    bool canSkipPostRenderCleanup() const override;
-    void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-                            const DisplaySettings& display,
-                            const std::vector<LayerSettings>& layers,
-                            const std::shared_ptr<ExternalTexture>& buffer,
-                            const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
-
-private:
-    friend class BindNativeBufferAsFramebuffer;
-
-    enum GlesVersion {
-        GLES_VERSION_1_0 = 0x10000,
-        GLES_VERSION_1_1 = 0x10001,
-        GLES_VERSION_2_0 = 0x20000,
-        GLES_VERSION_3_0 = 0x30000,
-    };
-
-    static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
-    static GlesVersion parseGlesVersion(const char* str);
-    static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
-                                       EGLContext shareContext,
-                                       std::optional<ContextPriority> contextPriority,
-                                       Protection protection);
-    static std::optional<RenderEngine::ContextPriority> createContextPriority(
-            const RenderEngineCreationArgs& args);
-    static EGLSurface createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
-                                                  int hwcFormat, Protection protection);
-    std::unique_ptr<Framebuffer> createFramebuffer();
-    std::unique_ptr<Image> createImage();
-    void checkErrors() const;
-    void checkErrors(const char* tag) const;
-    void setScissor(const Rect& region);
-    void disableScissor();
-    bool waitSync(EGLSyncKHR sync, EGLint flags);
-    status_t cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer)
-            EXCLUDES(mRenderingMutex);
-    void unbindExternalTextureBufferInternal(uint64_t bufferId) EXCLUDES(mRenderingMutex);
-    status_t bindFrameBuffer(Framebuffer* framebuffer);
-    void unbindFrameBuffer(Framebuffer* framebuffer);
-    void bindExternalTextureImage(uint32_t texName, const Image& image);
-    void bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
-                                   const sp<Fence>& fence) EXCLUDES(mRenderingMutex);
-    void cleanFramebufferCache() EXCLUDES(mFramebufferImageCacheMutex) override;
-
-    // A data space is considered HDR data space if it has BT2020 color space
-    // with PQ or HLG transfer function.
-    bool isHdrDataSpace(const ui::Dataspace dataSpace) const;
-    bool needsXYZTransformMatrix() const;
-    // Defines the viewport, and sets the projection matrix to the projection
-    // defined by the clip.
-    void setViewportAndProjection(Rect viewport, Rect clip);
-    // Evicts stale images from the buffer cache.
-    void evictImages(const std::vector<LayerSettings>& layers);
-    // Computes the cropping window for the layer and sets up cropping
-    // coordinates for the mesh.
-    FloatRect setupLayerCropping(const LayerSettings& layer, Mesh& mesh);
-
-    // We do a special handling for rounded corners when it's possible to turn off blending
-    // for the majority of the layer. The rounded corners needs to turn on blending such that
-    // we can set the alpha value correctly, however, only the corners need this, and since
-    // blending is an expensive operation, we want to turn off blending when it's not necessary.
-    void handleRoundedCorners(const DisplaySettings& display, const LayerSettings& layer,
-                              const Mesh& mesh);
-    base::unique_fd flush();
-    bool finish();
-    bool waitFence(base::unique_fd fenceFd);
-    void clearWithColor(float red, float green, float blue, float alpha);
-    void fillRegionWithColor(const Region& region, float red, float green, float blue, float alpha);
-    void handleShadow(const FloatRect& casterRect, float casterCornerRadius,
-                      const ShadowSettings& shadowSettings);
-    void setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
-                            const half4& color, float cornerRadius);
-    void setupLayerTexturing(const Texture& texture);
-    void setupFillWithColor(float r, float g, float b, float a);
-    void setColorTransform(const mat4& colorTransform);
-    void setDisplayColorTransform(const mat4& colorTransform);
-    void disableTexturing();
-    void disableBlending();
-    void setupCornerRadiusCropSize(float width, float height);
-
-    // HDR and color management related functions and state
-    void setSourceY410BT2020(bool enable);
-    void setSourceDataSpace(ui::Dataspace source);
-    void setOutputDataSpace(ui::Dataspace dataspace);
-    void setDisplayMaxLuminance(const float maxLuminance);
-
-    // drawing
-    void drawMesh(const Mesh& mesh);
-
-    EGLDisplay mEGLDisplay;
-    EGLConfig mEGLConfig;
-    EGLContext mEGLContext;
-    EGLSurface mStubSurface;
-    EGLContext mProtectedEGLContext;
-    EGLSurface mProtectedStubSurface;
-    GLint mMaxViewportDims[2];
-    GLint mMaxTextureSize;
-    GLuint mVpWidth;
-    GLuint mVpHeight;
-    Description mState;
-    std::unique_ptr<GLShadowTexture> mShadowTexture = nullptr;
-
-    mat4 mSrgbToXyz;
-    mat4 mDisplayP3ToXyz;
-    mat4 mBt2020ToXyz;
-    mat4 mXyzToSrgb;
-    mat4 mXyzToDisplayP3;
-    mat4 mXyzToBt2020;
-    mat4 mSrgbToDisplayP3;
-    mat4 mSrgbToBt2020;
-    mat4 mDisplayP3ToSrgb;
-    mat4 mDisplayP3ToBt2020;
-    mat4 mBt2020ToSrgb;
-    mat4 mBt2020ToDisplayP3;
-
-    bool mInProtectedContext = false;
-    // If set to true, then enables tracing flush() and finish() to systrace.
-    bool mTraceGpuCompletion = false;
-    // Maximum size of mFramebufferImageCache. If more images would be cached, then (approximately)
-    // the last recently used buffer should be kicked out.
-    uint32_t mFramebufferImageCacheSize = 0;
-
-    // Cache of output images, keyed by corresponding GraphicBuffer ID.
-    std::deque<std::pair<uint64_t, EGLImageKHR>> mFramebufferImageCache
-            GUARDED_BY(mFramebufferImageCacheMutex);
-    // The only reason why we have this mutex is so that we don't segfault when
-    // dumping info.
-    std::mutex mFramebufferImageCacheMutex;
-
-    // Current dataspace of layer being rendered
-    ui::Dataspace mDataSpace = ui::Dataspace::UNKNOWN;
-
-    // Current output dataspace of the render engine
-    ui::Dataspace mOutputDataSpace = ui::Dataspace::UNKNOWN;
-
-    // Whether device supports color management, currently color management
-    // supports sRGB, DisplayP3 color spaces.
-    const bool mUseColorManagement = false;
-
-    // Whether only shaders performing tone mapping from HDR to SDR will be generated on
-    // primeCache().
-    const bool mPrecacheToneMapperShaderOnly = false;
-
-    // Cache of GL images that we'll store per GraphicBuffer ID
-    std::unordered_map<uint64_t, std::unique_ptr<Image>> mImageCache GUARDED_BY(mRenderingMutex);
-    std::unordered_map<uint32_t, std::optional<uint64_t>> mTextureView;
-
-    // Mutex guarding rendering operations, so that:
-    // 1. GL operations aren't interleaved, and
-    // 2. Internal state related to rendering that is potentially modified by
-    // multiple threads is guaranteed thread-safe.
-    std::mutex mRenderingMutex;
-
-    std::unique_ptr<Framebuffer> mDrawingBuffer;
-    // this is a 1x1 RGB buffer, but over-allocate in case a driver wants more
-    // memory or if it needs to satisfy alignment requirements. In this case:
-    // assume that each channel requires 4 bytes, and add 3 additional bytes to
-    // ensure that we align on a word. Allocating 16 bytes will provide a
-    // guarantee that we don't clobber memory.
-    uint32_t mPlaceholderDrawBuffer[4];
-    // Placeholder buffer and image, similar to mPlaceholderDrawBuffer, but
-    // instead these are intended for cleaning up texture memory with the
-    // GL_TEXTURE_EXTERNAL_OES target.
-    ANativeWindowBuffer* mPlaceholderBuffer = nullptr;
-    EGLImage mPlaceholderImage = EGL_NO_IMAGE_KHR;
-    sp<Fence> mLastDrawFence;
-    // Store a separate boolean checking if prior resources were cleaned up, as
-    // devices that don't support native sync fences can't rely on a last draw
-    // fence that doesn't exist.
-    bool mPriorResourcesCleaned = true;
-
-    // Blur effect processor, only instantiated when a layer requests it.
-    BlurFilter* mBlurFilter = nullptr;
-
-    class FlushTracer {
-    public:
-        FlushTracer(GLESRenderEngine* engine);
-        ~FlushTracer();
-        void queueSync(EGLSyncKHR sync) EXCLUDES(mMutex);
-
-        struct QueueEntry {
-            EGLSyncKHR mSync = nullptr;
-            uint64_t mFrameNum = 0;
-        };
-
-    private:
-        void loop();
-        GLESRenderEngine* const mEngine;
-        std::thread mThread;
-        std::condition_variable_any mCondition;
-        std::mutex mMutex;
-        std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);
-        uint64_t mFramesQueued GUARDED_BY(mMutex) = 0;
-        bool mRunning = true;
-    };
-    friend class FlushTracer;
-    friend class ImageManager;
-    friend class GLFramebuffer;
-    friend class BlurFilter;
-    friend class GenericProgram;
-    std::unique_ptr<FlushTracer> mFlushTracer;
-    std::unique_ptr<ImageManager> mImageManager;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_GLESRENDERENGINE_H_ */
diff --git a/libs/renderengine/gl/GLFramebuffer.cpp b/libs/renderengine/gl/GLFramebuffer.cpp
deleted file mode 100644
index 58d6caa..0000000
--- a/libs/renderengine/gl/GLFramebuffer.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLFramebuffer.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-#include <gui/DebugEGLImageTracker.h>
-#include <nativebase/nativebase.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLFramebuffer::GLFramebuffer(GLESRenderEngine& engine)
-      : mEngine(engine), mEGLDisplay(engine.getEGLDisplay()), mEGLImage(EGL_NO_IMAGE_KHR) {
-    glGenTextures(1, &mTextureName);
-    glGenFramebuffers(1, &mFramebufferName);
-}
-
-GLFramebuffer::~GLFramebuffer() {
-    setNativeWindowBuffer(nullptr, false, false);
-    glDeleteFramebuffers(1, &mFramebufferName);
-    glDeleteTextures(1, &mTextureName);
-}
-
-bool GLFramebuffer::setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
-                                          const bool useFramebufferCache) {
-    ATRACE_CALL();
-    if (mEGLImage != EGL_NO_IMAGE_KHR) {
-        if (!usingFramebufferCache) {
-            eglDestroyImageKHR(mEGLDisplay, mEGLImage);
-            DEBUG_EGL_IMAGE_TRACKER_DESTROY();
-        }
-        mEGLImage = EGL_NO_IMAGE_KHR;
-        mBufferWidth = 0;
-        mBufferHeight = 0;
-    }
-
-    if (nativeBuffer) {
-        mEGLImage = mEngine.createFramebufferImageIfNeeded(nativeBuffer, isProtected,
-                                                           useFramebufferCache);
-        if (mEGLImage == EGL_NO_IMAGE_KHR) {
-            return false;
-        }
-        usingFramebufferCache = useFramebufferCache;
-        mBufferWidth = nativeBuffer->width;
-        mBufferHeight = nativeBuffer->height;
-    }
-    return true;
-}
-
-void GLFramebuffer::allocateBuffers(uint32_t width, uint32_t height, void* data) {
-    ATRACE_CALL();
-
-    glBindTexture(GL_TEXTURE_2D, mTextureName);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
-
-    mBufferHeight = height;
-    mBufferWidth = width;
-    mEngine.checkErrors("Allocating Fbo texture");
-
-    bind();
-    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextureName, 0);
-    mStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-    unbind();
-    glBindTexture(GL_TEXTURE_2D, 0);
-
-    if (mStatus != GL_FRAMEBUFFER_COMPLETE) {
-        ALOGE("Frame buffer is not complete. Error %d", mStatus);
-    }
-}
-
-void GLFramebuffer::bind() const {
-    glBindFramebuffer(GL_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::bindAsReadBuffer() const {
-    glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::bindAsDrawBuffer() const {
-    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::unbind() const {
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLFramebuffer.h b/libs/renderengine/gl/GLFramebuffer.h
deleted file mode 100644
index 6757695..0000000
--- a/libs/renderengine/gl/GLFramebuffer.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <renderengine/Framebuffer.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLFramebuffer : public renderengine::Framebuffer {
-public:
-    explicit GLFramebuffer(GLESRenderEngine& engine);
-    explicit GLFramebuffer(GLESRenderEngine& engine, bool multiTarget);
-    ~GLFramebuffer() override;
-
-    bool setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
-                               const bool useFramebufferCache) override;
-    void allocateBuffers(uint32_t width, uint32_t height, void* data = nullptr);
-    EGLImageKHR getEGLImage() const { return mEGLImage; }
-    uint32_t getTextureName() const { return mTextureName; }
-    uint32_t getFramebufferName() const { return mFramebufferName; }
-    int32_t getBufferHeight() const { return mBufferHeight; }
-    int32_t getBufferWidth() const { return mBufferWidth; }
-    GLenum getStatus() const { return mStatus; }
-    void bind() const;
-    void bindAsReadBuffer() const;
-    void bindAsDrawBuffer() const;
-    void unbind() const;
-
-private:
-    GLESRenderEngine& mEngine;
-    EGLDisplay mEGLDisplay;
-    EGLImageKHR mEGLImage;
-    bool usingFramebufferCache = false;
-    GLenum mStatus = GL_FRAMEBUFFER_UNSUPPORTED;
-    uint32_t mTextureName, mFramebufferName;
-
-    int32_t mBufferHeight = 0;
-    int32_t mBufferWidth = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLImage.cpp b/libs/renderengine/gl/GLImage.cpp
deleted file mode 100644
index 8497721..0000000
--- a/libs/renderengine/gl/GLImage.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLImage.h"
-
-#include <vector>
-
-#include <gui/DebugEGLImageTracker.h>
-#include <log/log.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "GLExtensions.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-static std::vector<EGLint> buildAttributeList(bool isProtected) {
-    std::vector<EGLint> attrs;
-    attrs.reserve(16);
-
-    attrs.push_back(EGL_IMAGE_PRESERVED_KHR);
-    attrs.push_back(EGL_TRUE);
-
-    if (isProtected && GLExtensions::getInstance().hasProtectedContent()) {
-        attrs.push_back(EGL_PROTECTED_CONTENT_EXT);
-        attrs.push_back(EGL_TRUE);
-    }
-
-    attrs.push_back(EGL_NONE);
-
-    return attrs;
-}
-
-GLImage::GLImage(const GLESRenderEngine& engine) : mEGLDisplay(engine.getEGLDisplay()) {}
-
-GLImage::~GLImage() {
-    setNativeWindowBuffer(nullptr, false);
-}
-
-bool GLImage::setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) {
-    ATRACE_CALL();
-    if (mEGLImage != EGL_NO_IMAGE_KHR) {
-        if (!eglDestroyImageKHR(mEGLDisplay, mEGLImage)) {
-            ALOGE("failed to destroy image: %#x", eglGetError());
-        }
-        DEBUG_EGL_IMAGE_TRACKER_DESTROY();
-        mEGLImage = EGL_NO_IMAGE_KHR;
-    }
-
-    if (buffer) {
-        std::vector<EGLint> attrs = buildAttributeList(isProtected);
-        mEGLImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
-                                      static_cast<EGLClientBuffer>(buffer), attrs.data());
-        if (mEGLImage == EGL_NO_IMAGE_KHR) {
-            ALOGE("failed to create EGLImage: %#x", eglGetError());
-            return false;
-        }
-        DEBUG_EGL_IMAGE_TRACKER_CREATE();
-        mProtected = isProtected;
-    }
-
-    return true;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLImage.h b/libs/renderengine/gl/GLImage.h
deleted file mode 100644
index 59d6ce3..0000000
--- a/libs/renderengine/gl/GLImage.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <android-base/macros.h>
-#include <renderengine/Image.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLImage : public renderengine::Image {
-public:
-    explicit GLImage(const GLESRenderEngine& engine);
-    ~GLImage() override;
-
-    bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) override;
-
-    EGLImageKHR getEGLImage() const { return mEGLImage; }
-    bool isProtected() const { return mProtected; }
-
-private:
-    EGLDisplay mEGLDisplay;
-    EGLImageKHR mEGLImage = EGL_NO_IMAGE_KHR;
-    bool mProtected = false;
-
-    DISALLOW_COPY_AND_ASSIGN(GLImage);
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowTexture.cpp b/libs/renderengine/gl/GLShadowTexture.cpp
deleted file mode 100644
index 2423a34..0000000
--- a/libs/renderengine/gl/GLShadowTexture.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-
-#include "GLShadowTexture.h"
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLShadowTexture::GLShadowTexture() {
-    fillShadowTextureData(mTextureData, SHADOW_TEXTURE_WIDTH);
-
-    glGenTextures(1, &mName);
-    glBindTexture(GL_TEXTURE_2D, mName);
-    glTexImage2D(GL_TEXTURE_2D, 0 /* base image level */, GL_ALPHA, SHADOW_TEXTURE_WIDTH,
-                 SHADOW_TEXTURE_HEIGHT, 0 /* border */, GL_ALPHA, GL_UNSIGNED_BYTE, mTextureData);
-    mTexture.init(Texture::TEXTURE_2D, mName);
-    mTexture.setFiltering(true);
-    mTexture.setDimensions(SHADOW_TEXTURE_WIDTH, 1);
-}
-
-GLShadowTexture::~GLShadowTexture() {
-    glDeleteTextures(1, &mName);
-}
-
-const Texture& GLShadowTexture::getTexture() {
-    return mTexture;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowTexture.h b/libs/renderengine/gl/GLShadowTexture.h
deleted file mode 100644
index 250a9d7..0000000
--- a/libs/renderengine/gl/GLShadowTexture.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <renderengine/Texture.h>
-#include <cstdint>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLShadowTexture {
-public:
-    GLShadowTexture();
-    ~GLShadowTexture();
-
-    const Texture& getTexture();
-
-private:
-    static constexpr int SHADOW_TEXTURE_WIDTH = 128;
-    static constexpr int SHADOW_TEXTURE_HEIGHT = 1;
-
-    GLuint mName;
-    Texture mTexture;
-    uint8_t mTextureData[SHADOW_TEXTURE_WIDTH];
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.cpp b/libs/renderengine/gl/GLShadowVertexGenerator.cpp
deleted file mode 100644
index 3181f9b..0000000
--- a/libs/renderengine/gl/GLShadowVertexGenerator.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/Mesh.h>
-
-#include <math/vec4.h>
-
-#include <ui/Rect.h>
-#include <ui/Transform.h>
-
-#include "GLShadowVertexGenerator.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLShadowVertexGenerator::GLShadowVertexGenerator(const FloatRect& casterRect,
-                                                 float casterCornerRadius, float casterZ,
-                                                 bool casterIsTranslucent, const vec4& ambientColor,
-                                                 const vec4& spotColor, const vec3& lightPosition,
-                                                 float lightRadius) {
-    mDrawAmbientShadow = ambientColor.a > 0.f;
-    mDrawSpotShadow = spotColor.a > 0.f;
-
-    // Generate geometries and find number of vertices to generate
-    if (mDrawAmbientShadow) {
-        mAmbientShadowGeometry = getAmbientShadowGeometry(casterRect, casterCornerRadius, casterZ,
-                                                          casterIsTranslucent, ambientColor);
-        mAmbientShadowVertexCount = getVertexCountForGeometry(*mAmbientShadowGeometry.get());
-        mAmbientShadowIndexCount = getIndexCountForGeometry(*mAmbientShadowGeometry.get());
-    } else {
-        mAmbientShadowVertexCount = 0;
-        mAmbientShadowIndexCount = 0;
-    }
-
-    if (mDrawSpotShadow) {
-        mSpotShadowGeometry =
-                getSpotShadowGeometry(casterRect, casterCornerRadius, casterZ, casterIsTranslucent,
-                                      spotColor, lightPosition, lightRadius);
-        mSpotShadowVertexCount = getVertexCountForGeometry(*mSpotShadowGeometry.get());
-        mSpotShadowIndexCount = getIndexCountForGeometry(*mSpotShadowGeometry.get());
-    } else {
-        mSpotShadowVertexCount = 0;
-        mSpotShadowIndexCount = 0;
-    }
-}
-
-size_t GLShadowVertexGenerator::getVertexCount() const {
-    return mAmbientShadowVertexCount + mSpotShadowVertexCount;
-}
-
-size_t GLShadowVertexGenerator::getIndexCount() const {
-    return mAmbientShadowIndexCount + mSpotShadowIndexCount;
-}
-
-void GLShadowVertexGenerator::fillVertices(Mesh::VertexArray<vec2>& position,
-                                           Mesh::VertexArray<vec4>& color,
-                                           Mesh::VertexArray<vec3>& params) const {
-    if (mDrawAmbientShadow) {
-        fillVerticesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowVertexCount, position,
-                                color, params);
-    }
-    if (mDrawSpotShadow) {
-        fillVerticesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowVertexCount,
-                                Mesh::VertexArray<vec2>(position, mAmbientShadowVertexCount),
-                                Mesh::VertexArray<vec4>(color, mAmbientShadowVertexCount),
-                                Mesh::VertexArray<vec3>(params, mAmbientShadowVertexCount));
-    }
-}
-
-void GLShadowVertexGenerator::fillIndices(uint16_t* indices) const {
-    if (mDrawAmbientShadow) {
-        fillIndicesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowIndexCount,
-                               0 /* starting vertex offset */, indices);
-    }
-    if (mDrawSpotShadow) {
-        fillIndicesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowIndexCount,
-                               mAmbientShadowVertexCount /* starting vertex offset */,
-                               &(indices[mAmbientShadowIndexCount]));
-    }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.h b/libs/renderengine/gl/GLShadowVertexGenerator.h
deleted file mode 100644
index 112f976..0000000
--- a/libs/renderengine/gl/GLShadowVertexGenerator.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <math/vec4.h>
-#include <ui/Rect.h>
-
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-
-class Mesh;
-
-namespace gl {
-
-/**
- * Generates gl attributes required to draw shadow spot and/or ambient shadows.
- *
- * Each shadow can support different colors. This class generates three vertex attributes for
- * each shadow, its position, color and shadow params(offset and distance). These can be sent
- * using a single glDrawElements call.
- */
-class GLShadowVertexGenerator {
-public:
-    GLShadowVertexGenerator(const FloatRect& casterRect, float casterCornerRadius, float casterZ,
-                            bool casterIsTranslucent, const vec4& ambientColor,
-                            const vec4& spotColor, const vec3& lightPosition, float lightRadius);
-    ~GLShadowVertexGenerator() = default;
-
-    size_t getVertexCount() const;
-    size_t getIndexCount() const;
-    void fillVertices(Mesh::VertexArray<vec2>& position, Mesh::VertexArray<vec4>& color,
-                      Mesh::VertexArray<vec3>& params) const;
-    void fillIndices(uint16_t* indices) const;
-
-private:
-    bool mDrawAmbientShadow;
-    std::unique_ptr<Geometry> mAmbientShadowGeometry;
-    int mAmbientShadowVertexCount = 0;
-    int mAmbientShadowIndexCount = 0;
-
-    bool mDrawSpotShadow;
-    std::unique_ptr<Geometry> mSpotShadowGeometry;
-    int mSpotShadowVertexCount = 0;
-    int mSpotShadowIndexCount = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.cpp b/libs/renderengine/gl/GLSkiaShadowPort.cpp
deleted file mode 100644
index da8b435..0000000
--- a/libs/renderengine/gl/GLSkiaShadowPort.cpp
+++ /dev/null
@@ -1,656 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <math/vec4.h>
-
-#include <renderengine/Mesh.h>
-
-#include <ui/Rect.h>
-#include <ui/Transform.h>
-
-#include <utils/Log.h>
-
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * The shadow geometry logic and vertex generation code has been ported from skia shadow
- * fast path OpenGL implementation to draw shadows around rects and rounded rects including
- * circles.
- *
- * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
- *
- * Modifications made:
- * - Switched to using std lib math functions
- * - Fall off function is implemented in vertex shader rather than a shadow texture
- * - Removed transformations applied on the caster rect since the caster will be in local
- *   coordinate space and will be transformed by the vertex shader.
- */
-
-static inline float divide_and_pin(float numer, float denom, float min, float max) {
-    if (denom == 0.0f) return min;
-    return std::clamp(numer / denom, min, max);
-}
-
-static constexpr auto SK_ScalarSqrt2 = 1.41421356f;
-static constexpr auto kAmbientHeightFactor = 1.0f / 128.0f;
-static constexpr auto kAmbientGeomFactor = 64.0f;
-// Assuming that we have a light height of 600 for the spot shadow,
-// the spot values will reach their maximum at a height of approximately 292.3077.
-// We'll round up to 300 to keep it simple.
-static constexpr auto kMaxAmbientRadius = 300 * kAmbientHeightFactor * kAmbientGeomFactor;
-
-inline float AmbientBlurRadius(float height) {
-    return std::min(height * kAmbientHeightFactor * kAmbientGeomFactor, kMaxAmbientRadius);
-}
-inline float AmbientRecipAlpha(float height) {
-    return 1.0f + std::max(height * kAmbientHeightFactor, 0.0f);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Circle Data
-//
-// We have two possible cases for geometry for a circle:
-
-// In the case of a normal fill, we draw geometry for the circle as an octagon.
-static const uint16_t gFillCircleIndices[] = {
-        // enter the octagon
-        // clang-format off
-         0, 1, 8, 1, 2, 8,
-         2, 3, 8, 3, 4, 8,
-         4, 5, 8, 5, 6, 8,
-         6, 7, 8, 7, 0, 8,
-        // clang-format on
-};
-
-// For stroked circles, we use two nested octagons.
-static const uint16_t gStrokeCircleIndices[] = {
-        // enter the octagon
-        // clang-format off
-         0, 1,  9, 0,  9,  8,
-         1, 2, 10, 1, 10,  9,
-         2, 3, 11, 2, 11, 10,
-         3, 4, 12, 3, 12, 11,
-         4, 5, 13, 4, 13, 12,
-         5, 6, 14, 5, 14, 13,
-         6, 7, 15, 6, 15, 14,
-         7, 0,  8, 7,  8, 15,
-        // clang-format on
-};
-
-#define SK_ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
-static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
-static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
-static const int kVertsPerStrokeCircle = 16;
-static const int kVertsPerFillCircle = 9;
-
-static int circle_type_to_vert_count(bool stroked) {
-    return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
-}
-
-static int circle_type_to_index_count(bool stroked) {
-    return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
-}
-
-static const uint16_t* circle_type_to_indices(bool stroked) {
-    return stroked ? gStrokeCircleIndices : gFillCircleIndices;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// RoundRect Data
-//
-// The geometry for a shadow roundrect is similar to a 9-patch:
-//    ____________
-//   |_|________|_|
-//   | |        | |
-//   | |        | |
-//   | |        | |
-//   |_|________|_|
-//   |_|________|_|
-//
-// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
-// shows the upper part of the upper left corner. The bottom triangle would similarly be split
-// into two triangles.)
-//    ________
-//   |\  \   |
-//   |  \ \  |
-//   |    \\ |
-//   |      \|
-//   --------
-//
-// The center of the fan handles the curve of the corner. For roundrects where the stroke width
-// is greater than the corner radius, the outer triangles blend from the curve to the straight
-// sides. Otherwise these triangles will be degenerate.
-//
-// In the case where the stroke width is greater than the corner radius and the
-// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
-// This rectangle extends the coverage values of the center edges of the 9-patch.
-//    ____________
-//   |_|________|_|
-//   | |\ ____ /| |
-//   | | |    | | |
-//   | | |____| | |
-//   |_|/______\|_|
-//   |_|________|_|
-//
-// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
-
-static const uint16_t gRRectIndices[] = {
-        // clang-format off
-     // overstroke quads
-     // we place this at the beginning so that we can skip these indices when rendering as filled
-     0, 6, 25, 0, 25, 24,
-     6, 18, 27, 6, 27, 25,
-     18, 12, 26, 18, 26, 27,
-     12, 0, 24, 12, 24, 26,
-
-     // corners
-     0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
-     6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
-     12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
-     18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
-
-     // edges
-     0, 5, 11, 0, 11, 6,
-     6, 7, 19, 6, 19, 18,
-     18, 23, 17, 18, 17, 12,
-     12, 13, 1, 12, 1, 0,
-
-     // fill quad
-     // we place this at the end so that we can skip these indices when rendering as stroked
-     0, 6, 18, 0, 18, 12,
-        // clang-format on
-};
-
-// overstroke count
-static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
-// simple stroke count skips overstroke indices
-static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4;
-// fill count adds final quad to stroke count
-static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
-static const int kVertsPerStrokeRRect = 24;
-static const int kVertsPerOverstrokeRRect = 28;
-static const int kVertsPerFillRRect = 24;
-
-static int rrect_type_to_vert_count(RRectType type) {
-    switch (type) {
-        case kFill_RRectType:
-            return kVertsPerFillRRect;
-        case kStroke_RRectType:
-            return kVertsPerStrokeRRect;
-        case kOverstroke_RRectType:
-            return kVertsPerOverstrokeRRect;
-    }
-    ALOGE("Invalid rect type: %d", type);
-    return -1;
-}
-
-static int rrect_type_to_index_count(RRectType type) {
-    switch (type) {
-        case kFill_RRectType:
-            return kIndicesPerFillRRect;
-        case kStroke_RRectType:
-            return kIndicesPerStrokeRRect;
-        case kOverstroke_RRectType:
-            return kIndicesPerOverstrokeRRect;
-    }
-    ALOGE("Invalid rect type: %d", type);
-    return -1;
-}
-
-static const uint16_t* rrect_type_to_indices(RRectType type) {
-    switch (type) {
-        case kFill_RRectType:
-        case kStroke_RRectType:
-            return gRRectIndices + 6 * 4;
-        case kOverstroke_RRectType:
-            return gRRectIndices;
-    }
-    ALOGE("Invalid rect type: %d", type);
-    return nullptr;
-}
-
-static void fillInCircleVerts(const Geometry& args, bool isStroked,
-                              Mesh::VertexArray<vec2>& position,
-                              Mesh::VertexArray<vec4>& shadowColor,
-                              Mesh::VertexArray<vec3>& shadowParams) {
-    vec4 color = args.fColor;
-    float outerRadius = args.fOuterRadius;
-    float innerRadius = args.fInnerRadius;
-    float blurRadius = args.fBlurRadius;
-    float distanceCorrection = outerRadius / blurRadius;
-
-    const FloatRect& bounds = args.fDevBounds;
-
-    // The inner radius in the vertex data must be specified in normalized space.
-    innerRadius = innerRadius / outerRadius;
-
-    vec2 center = vec2(bounds.getWidth() / 2.0f, bounds.getHeight() / 2.0f);
-    float halfWidth = 0.5f * bounds.getWidth();
-    float octOffset = 0.41421356237f; // sqrt(2) - 1
-    int vertexCount = 0;
-
-    position[vertexCount] = center + vec2(-octOffset * halfWidth, -halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(-octOffset, -1, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(octOffset * halfWidth, -halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(octOffset, -1, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(halfWidth, -octOffset * halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(1, -octOffset, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(halfWidth, octOffset * halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(1, octOffset, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(octOffset * halfWidth, halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(octOffset, 1, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(-octOffset * halfWidth, halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(-octOffset, 1, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(-halfWidth, octOffset * halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(-1, octOffset, distanceCorrection);
-    vertexCount++;
-
-    position[vertexCount] = center + vec2(-halfWidth, -octOffset * halfWidth);
-    shadowColor[vertexCount] = color;
-    shadowParams[vertexCount] = vec3(-1, -octOffset, distanceCorrection);
-    vertexCount++;
-
-    if (isStroked) {
-        // compute the inner ring
-
-        // cosine and sine of pi/8
-        float c = 0.923579533f;
-        float s = 0.382683432f;
-        float r = args.fInnerRadius;
-
-        position[vertexCount] = center + vec2(-s * r, -c * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(-s * innerRadius, -c * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(s * r, -c * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(s * innerRadius, -c * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(c * r, -s * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(c * innerRadius, -s * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(c * r, s * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(c * innerRadius, s * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(s * r, c * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(s * innerRadius, c * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(-s * r, c * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(-s * innerRadius, c * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(-c * r, s * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(-c * innerRadius, s * innerRadius, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = center + vec2(-c * r, -s * r);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(-c * innerRadius, -s * innerRadius, distanceCorrection);
-        vertexCount++;
-    } else {
-        // filled
-        position[vertexCount] = center;
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
-        vertexCount++;
-    }
-}
-
-static void fillInRRectVerts(const Geometry& args, Mesh::VertexArray<vec2>& position,
-                             Mesh::VertexArray<vec4>& shadowColor,
-                             Mesh::VertexArray<vec3>& shadowParams) {
-    vec4 color = args.fColor;
-    float outerRadius = args.fOuterRadius;
-
-    const FloatRect& bounds = args.fDevBounds;
-
-    float umbraInset = args.fUmbraInset;
-    float minDim = 0.5f * std::min(bounds.getWidth(), bounds.getHeight());
-    if (umbraInset > minDim) {
-        umbraInset = minDim;
-    }
-
-    float xInner[4] = {bounds.left + umbraInset, bounds.right - umbraInset,
-                       bounds.left + umbraInset, bounds.right - umbraInset};
-    float xMid[4] = {bounds.left + outerRadius, bounds.right - outerRadius,
-                     bounds.left + outerRadius, bounds.right - outerRadius};
-    float xOuter[4] = {bounds.left, bounds.right, bounds.left, bounds.right};
-    float yInner[4] = {bounds.top + umbraInset, bounds.top + umbraInset, bounds.bottom - umbraInset,
-                       bounds.bottom - umbraInset};
-    float yMid[4] = {bounds.top + outerRadius, bounds.top + outerRadius,
-                     bounds.bottom - outerRadius, bounds.bottom - outerRadius};
-    float yOuter[4] = {bounds.top, bounds.top, bounds.bottom, bounds.bottom};
-
-    float blurRadius = args.fBlurRadius;
-
-    // In the case where we have to inset more for the umbra, our two triangles in the
-    // corner get skewed to a diamond rather than a square. To correct for that,
-    // we also skew the vectors we send to the shader that help define the circle.
-    // By doing so, we end up with a quarter circle in the corner rather than the
-    // elliptical curve.
-
-    // This is a bit magical, but it gives us the correct results at extrema:
-    //   a) umbraInset == outerRadius produces an orthogonal vector
-    //   b) outerRadius == 0 produces a diagonal vector
-    // And visually the corner looks correct.
-    vec2 outerVec = vec2(outerRadius - umbraInset, -outerRadius - umbraInset);
-    outerVec = normalize(outerVec);
-    // We want the circle edge to fall fractionally along the diagonal at
-    //      (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
-    //
-    // Setting the components of the diagonal offset to the following value will give us that.
-    float diagVal = umbraInset / (SK_ScalarSqrt2 * (outerRadius - umbraInset) - outerRadius);
-    vec2 diagVec = vec2(diagVal, diagVal);
-    float distanceCorrection = umbraInset / blurRadius;
-
-    int vertexCount = 0;
-    // build corner by corner
-    for (int i = 0; i < 4; ++i) {
-        // inner point
-        position[vertexCount] = vec2(xInner[i], yInner[i]);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
-        vertexCount++;
-
-        // outer points
-        position[vertexCount] = vec2(xOuter[i], yInner[i]);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = vec2(xOuter[i], yMid[i]);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = vec2(xOuter[i], yOuter[i]);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(diagVec.x, diagVec.y, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = vec2(xMid[i], yOuter[i]);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
-        vertexCount++;
-
-        position[vertexCount] = vec2(xInner[i], yOuter[i]);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
-        vertexCount++;
-    }
-
-    // Add the additional vertices for overstroked rrects.
-    // Effectively this is an additional stroked rrect, with its
-    // parameters equal to those in the center of the 9-patch. This will
-    // give constant values across this inner ring.
-    if (kOverstroke_RRectType == args.fType) {
-        float inset = umbraInset + args.fInnerRadius;
-
-        // TL
-        position[vertexCount] = vec2(bounds.left + inset, bounds.top + inset);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
-        vertexCount++;
-
-        // TR
-        position[vertexCount] = vec2(bounds.right - inset, bounds.top + inset);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
-        vertexCount++;
-
-        // BL
-        position[vertexCount] = vec2(bounds.left + inset, bounds.bottom - inset);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
-        vertexCount++;
-
-        // BR
-        position[vertexCount] = vec2(bounds.right - inset, bounds.bottom - inset);
-        shadowColor[vertexCount] = color;
-        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
-        vertexCount++;
-    }
-}
-
-int getVertexCountForGeometry(const Geometry& shadowGeometry) {
-    if (shadowGeometry.fIsCircle) {
-        return circle_type_to_vert_count(shadowGeometry.fType);
-    }
-
-    return rrect_type_to_vert_count(shadowGeometry.fType);
-}
-
-int getIndexCountForGeometry(const Geometry& shadowGeometry) {
-    if (shadowGeometry.fIsCircle) {
-        return circle_type_to_index_count(kStroke_RRectType == shadowGeometry.fType);
-    }
-
-    return rrect_type_to_index_count(shadowGeometry.fType);
-}
-
-void fillVerticesForGeometry(const Geometry& shadowGeometry, int /* vertexCount */,
-                             Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
-                             Mesh::VertexArray<vec3> shadowParams) {
-    if (shadowGeometry.fIsCircle) {
-        fillInCircleVerts(shadowGeometry, shadowGeometry.fIsStroked, position, shadowColor,
-                          shadowParams);
-    } else {
-        fillInRRectVerts(shadowGeometry, position, shadowColor, shadowParams);
-    }
-}
-
-void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
-                            int startingVertexOffset, uint16_t* indices) {
-    if (shadowGeometry.fIsCircle) {
-        const uint16_t* primIndices = circle_type_to_indices(shadowGeometry.fIsStroked);
-        for (int i = 0; i < indexCount; ++i) {
-            indices[i] = primIndices[i] + startingVertexOffset;
-        }
-    } else {
-        const uint16_t* primIndices = rrect_type_to_indices(shadowGeometry.fType);
-        for (int i = 0; i < indexCount; ++i) {
-            indices[i] = primIndices[i] + startingVertexOffset;
-        }
-    }
-}
-
-inline void GetSpotParams(float occluderZ, float lightX, float lightY, float lightZ,
-                          float lightRadius, float& blurRadius, float& scale, vec2& translate) {
-    float zRatio = divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
-    blurRadius = lightRadius * zRatio;
-    scale = divide_and_pin(lightZ, lightZ - occluderZ, 1.0f, 1.95f);
-    translate.x = -zRatio * lightX;
-    translate.y = -zRatio * lightY;
-}
-
-static std::unique_ptr<Geometry> getShadowGeometry(const vec4& color, const FloatRect& devRect,
-                                                   float devRadius, float blurRadius,
-                                                   float insetWidth) {
-    // An insetWidth > 1/2 rect width or height indicates a simple fill.
-    const bool isCircle = ((devRadius >= devRect.getWidth()) && (devRadius >= devRect.getHeight()));
-
-    FloatRect bounds = devRect;
-    float innerRadius = 0.0f;
-    float outerRadius = devRadius;
-    float umbraInset;
-
-    RRectType type = kFill_RRectType;
-    if (isCircle) {
-        umbraInset = 0;
-    } else {
-        umbraInset = std::max(outerRadius, blurRadius);
-    }
-
-    // If stroke is greater than width or height, this is still a fill,
-    // otherwise we compute stroke params.
-    if (isCircle) {
-        innerRadius = devRadius - insetWidth;
-        type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
-    } else {
-        if (insetWidth <= 0.5f * std::min(devRect.getWidth(), devRect.getHeight())) {
-            // We don't worry about a real inner radius, we just need to know if we
-            // need to create overstroke vertices.
-            innerRadius = std::max(insetWidth - umbraInset, 0.0f);
-            type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
-        }
-    }
-    const bool isStroked = (kStroke_RRectType == type);
-    return std::make_unique<Geometry>(Geometry{color, outerRadius, umbraInset, innerRadius,
-                                               blurRadius, bounds, type, isCircle, isStroked});
-}
-
-std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
-                                                   float casterCornerRadius, float casterZ,
-                                                   bool casterIsTranslucent,
-                                                   const vec4& ambientColor) {
-    float devSpaceInsetWidth = AmbientBlurRadius(casterZ);
-    const float umbraRecipAlpha = AmbientRecipAlpha(casterZ);
-    const float devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha;
-
-    // Outset the shadow rrect to the border of the penumbra
-    float ambientPathOutset = devSpaceInsetWidth;
-    FloatRect outsetRect(casterRect);
-    outsetRect.left -= ambientPathOutset;
-    outsetRect.top -= ambientPathOutset;
-    outsetRect.right += ambientPathOutset;
-    outsetRect.bottom += ambientPathOutset;
-
-    float outsetRad = casterCornerRadius + ambientPathOutset;
-    if (casterIsTranslucent) {
-        // set a large inset to force a fill
-        devSpaceInsetWidth = outsetRect.getWidth();
-    }
-
-    return getShadowGeometry(ambientColor, outsetRect, std::abs(outsetRad), devSpaceAmbientBlur,
-                             std::abs(devSpaceInsetWidth));
-}
-
-std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
-                                                float casterCornerRadius, float casterZ,
-                                                bool casterIsTranslucent, const vec4& spotColor,
-                                                const vec3& lightPosition, float lightRadius) {
-    float devSpaceSpotBlur;
-    float spotScale;
-    vec2 spotOffset;
-    GetSpotParams(casterZ, lightPosition.x, lightPosition.y, lightPosition.z, lightRadius,
-                  devSpaceSpotBlur, spotScale, spotOffset);
-    // handle scale of radius due to CTM
-    const float srcSpaceSpotBlur = devSpaceSpotBlur;
-
-    // Adjust translate for the effect of the scale.
-    spotOffset.x += spotScale;
-    spotOffset.y += spotScale;
-
-    // Compute the transformed shadow rect
-    ui::Transform shadowTransform;
-    shadowTransform.set(spotOffset.x, spotOffset.y);
-    shadowTransform.set(spotScale, 0, 0, spotScale);
-    FloatRect spotShadowRect = shadowTransform.transform(casterRect);
-    float spotShadowRadius = casterCornerRadius * spotScale;
-
-    // Compute the insetWidth
-    float blurOutset = srcSpaceSpotBlur;
-    float insetWidth = blurOutset;
-    if (casterIsTranslucent) {
-        // If transparent, just do a fill
-        insetWidth += spotShadowRect.getWidth();
-    } else {
-        // For shadows, instead of using a stroke we specify an inset from the penumbra
-        // border. We want to extend this inset area so that it meets up with the caster
-        // geometry. The inset geometry will by default already be inset by the blur width.
-        //
-        // We compare the min and max corners inset by the radius between the original
-        // rrect and the shadow rrect. The distance between the two plus the difference
-        // between the scaled radius and the original radius gives the distance from the
-        // transformed shadow shape to the original shape in that corner. The max
-        // of these gives the maximum distance we need to cover.
-        //
-        // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to
-        // that to get the full insetWidth.
-        float maxOffset;
-        if (casterCornerRadius <= 0.f) {
-            // Manhattan distance works better for rects
-            maxOffset = std::max(std::max(std::abs(spotShadowRect.left - casterRect.left),
-                                          std::abs(spotShadowRect.top - casterRect.top)),
-                                 std::max(std::abs(spotShadowRect.right - casterRect.right),
-                                          std::abs(spotShadowRect.bottom - casterRect.bottom)));
-        } else {
-            float dr = spotShadowRadius - casterCornerRadius;
-            vec2 upperLeftOffset = vec2(spotShadowRect.left - casterRect.left + dr,
-                                        spotShadowRect.top - casterRect.top + dr);
-            vec2 lowerRightOffset = vec2(spotShadowRect.right - casterRect.right - dr,
-                                         spotShadowRect.bottom - casterRect.bottom - dr);
-            maxOffset = sqrt(std::max(dot(upperLeftOffset, lowerRightOffset),
-                                      dot(lowerRightOffset, lowerRightOffset))) +
-                    dr;
-        }
-        insetWidth += std::max(blurOutset, maxOffset);
-    }
-
-    // Outset the shadow rrect to the border of the penumbra
-    spotShadowRadius += blurOutset;
-    spotShadowRect.left -= blurOutset;
-    spotShadowRect.top -= blurOutset;
-    spotShadowRect.right += blurOutset;
-    spotShadowRect.bottom += blurOutset;
-
-    return getShadowGeometry(spotColor, spotShadowRect, std::abs(spotShadowRadius),
-                             2.0f * devSpaceSpotBlur, std::abs(insetWidth));
-}
-
-void fillShadowTextureData(uint8_t* data, size_t shadowTextureWidth) {
-    for (int i = 0; i < shadowTextureWidth; i++) {
-        const float d = 1 - i / ((shadowTextureWidth * 1.0f) - 1.0f);
-        data[i] = static_cast<uint8_t>((exp(-4.0f * d * d) - 0.018f) * 255);
-    }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.h b/libs/renderengine/gl/GLSkiaShadowPort.h
deleted file mode 100644
index 912c8bb..0000000
--- a/libs/renderengine/gl/GLSkiaShadowPort.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <math/vec4.h>
-#include <renderengine/Mesh.h>
-#include <ui/Rect.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * The shadow geometry logic and vertex generation code has been ported from skia shadow
- * fast path OpenGL implementation to draw shadows around rects and rounded rects including
- * circles.
- *
- * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
- *
- * Modifications made:
- * - Switched to using std lib math functions
- * - Fall off function is implemented in vertex shader rather than a shadow texture
- * - Removed transformations applied on the caster rect since the caster will be in local
- *   coordinate space and will be transformed by the vertex shader.
- */
-
-enum RRectType {
-    kFill_RRectType,
-    kStroke_RRectType,
-    kOverstroke_RRectType,
-};
-
-struct Geometry {
-    vec4 fColor;
-    float fOuterRadius;
-    float fUmbraInset;
-    float fInnerRadius;
-    float fBlurRadius;
-    FloatRect fDevBounds;
-    RRectType fType;
-    bool fIsCircle;
-    bool fIsStroked;
-};
-
-std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
-                                                float casterCornerRadius, float casterZ,
-                                                bool casterIsTranslucent, const vec4& spotColor,
-                                                const vec3& lightPosition, float lightRadius);
-
-std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
-                                                   float casterCornerRadius, float casterZ,
-                                                   bool casterIsTranslucent,
-                                                   const vec4& ambientColor);
-
-int getVertexCountForGeometry(const Geometry& shadowGeometry);
-
-int getIndexCountForGeometry(const Geometry& shadowGeometry);
-
-void fillVerticesForGeometry(const Geometry& shadowGeometry, int vertexCount,
-                             Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
-                             Mesh::VertexArray<vec3> shadowParams);
-
-void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
-                            int startingVertexOffset, uint16_t* indices);
-
-/**
- * Maps shadow geometry 'alpha' varying (1 for darkest, 0 for transparent) to
- * darkness at that spot. Values are determined by an exponential falloff
- * function provided by UX.
- *
- * The texture is used for quick lookup in theshadow shader.
- *
- * textureData - filled with shadow texture data that needs to be at least of
- *               size textureWidth
- *
- * textureWidth - width of the texture, height is always 1
- */
-void fillShadowTextureData(uint8_t* textureData, size_t textureWidth);
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLVertexBuffer.cpp b/libs/renderengine/gl/GLVertexBuffer.cpp
deleted file mode 100644
index e50c471..0000000
--- a/libs/renderengine/gl/GLVertexBuffer.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLVertexBuffer.h"
-
-#include <GLES/gl.h>
-#include <GLES2/gl2.h>
-#include <nativebase/nativebase.h>
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLVertexBuffer::GLVertexBuffer() {
-    glGenBuffers(1, &mBufferName);
-}
-
-GLVertexBuffer::~GLVertexBuffer() {
-    glDeleteBuffers(1, &mBufferName);
-}
-
-void GLVertexBuffer::allocateBuffers(const GLfloat data[], const GLuint size) {
-    ATRACE_CALL();
-    bind();
-    glBufferData(GL_ARRAY_BUFFER, size * sizeof(GLfloat), data, GL_STATIC_DRAW);
-    unbind();
-}
-
-void GLVertexBuffer::bind() const {
-    glBindBuffer(GL_ARRAY_BUFFER, mBufferName);
-}
-
-void GLVertexBuffer::unbind() const {
-    glBindBuffer(GL_ARRAY_BUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLVertexBuffer.h b/libs/renderengine/gl/GLVertexBuffer.h
deleted file mode 100644
index c0fd0c1..0000000
--- a/libs/renderengine/gl/GLVertexBuffer.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLVertexBuffer {
-public:
-    explicit GLVertexBuffer();
-    ~GLVertexBuffer();
-
-    void allocateBuffers(const GLfloat data[], const GLuint size);
-    uint32_t getBufferName() const { return mBufferName; }
-    void bind() const;
-    void unbind() const;
-
-private:
-    uint32_t mBufferName;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ImageManager.cpp b/libs/renderengine/gl/ImageManager.cpp
deleted file mode 100644
index 6256649..0000000
--- a/libs/renderengine/gl/ImageManager.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <pthread.h>
-
-#include <processgroup/sched_policy.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "ImageManager.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-ImageManager::ImageManager(GLESRenderEngine* engine) : mEngine(engine) {}
-
-void ImageManager::initThread() {
-    mThread = std::thread([this]() { threadMain(); });
-    pthread_setname_np(mThread.native_handle(), "ImageManager");
-    // Use SCHED_FIFO to minimize jitter
-    struct sched_param param = {0};
-    param.sched_priority = 2;
-    if (pthread_setschedparam(mThread.native_handle(), SCHED_FIFO, &param) != 0) {
-        ALOGE("Couldn't set SCHED_FIFO for ImageManager");
-    }
-}
-
-ImageManager::~ImageManager() {
-    {
-        std::lock_guard<std::mutex> lock(mMutex);
-        mRunning = false;
-    }
-    mCondition.notify_all();
-    if (mThread.joinable()) {
-        mThread.join();
-    }
-}
-
-void ImageManager::cacheAsync(const sp<GraphicBuffer>& buffer,
-                              const std::shared_ptr<Barrier>& barrier) {
-    if (buffer == nullptr) {
-        {
-            std::lock_guard<std::mutex> lock(barrier->mutex);
-            barrier->isOpen = true;
-            barrier->result = BAD_VALUE;
-        }
-        barrier->condition.notify_one();
-        return;
-    }
-    ATRACE_CALL();
-    QueueEntry entry = {QueueEntry::Operation::Insert, buffer, buffer->getId(), barrier};
-    queueOperation(std::move(entry));
-}
-
-status_t ImageManager::cache(const sp<GraphicBuffer>& buffer) {
-    ATRACE_CALL();
-    auto barrier = std::make_shared<Barrier>();
-    cacheAsync(buffer, barrier);
-    std::lock_guard<std::mutex> lock(barrier->mutex);
-    barrier->condition.wait(barrier->mutex,
-                            [&]() REQUIRES(barrier->mutex) { return barrier->isOpen; });
-    return barrier->result;
-}
-
-void ImageManager::releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) {
-    ATRACE_CALL();
-    QueueEntry entry = {QueueEntry::Operation::Delete, nullptr, bufferId, barrier};
-    queueOperation(std::move(entry));
-}
-
-void ImageManager::queueOperation(const QueueEntry&& entry) {
-    {
-        std::lock_guard<std::mutex> lock(mMutex);
-        mQueue.emplace(entry);
-        ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
-    }
-    mCondition.notify_one();
-}
-
-void ImageManager::threadMain() {
-    set_sched_policy(0, SP_FOREGROUND);
-    bool run;
-    {
-        std::lock_guard<std::mutex> lock(mMutex);
-        run = mRunning;
-    }
-    while (run) {
-        QueueEntry entry;
-        {
-            std::lock_guard<std::mutex> lock(mMutex);
-            mCondition.wait(mMutex,
-                            [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
-            run = mRunning;
-
-            if (!mRunning) {
-                // if mRunning is false, then ImageManager is being destroyed, so
-                // bail out now.
-                break;
-            }
-
-            entry = mQueue.front();
-            mQueue.pop();
-            ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
-        }
-
-        status_t result = NO_ERROR;
-        switch (entry.op) {
-            case QueueEntry::Operation::Delete:
-                mEngine->unbindExternalTextureBufferInternal(entry.bufferId);
-                break;
-            case QueueEntry::Operation::Insert:
-                result = mEngine->cacheExternalTextureBufferInternal(entry.buffer);
-                break;
-        }
-        if (entry.barrier != nullptr) {
-            {
-                std::lock_guard<std::mutex> entryLock(entry.barrier->mutex);
-                entry.barrier->result = result;
-                entry.barrier->isOpen = true;
-            }
-            entry.barrier->condition.notify_one();
-        }
-    }
-
-    ALOGD("Reached end of threadMain, terminating ImageManager thread!");
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ImageManager.h b/libs/renderengine/gl/ImageManager.h
deleted file mode 100644
index be67de8..0000000
--- a/libs/renderengine/gl/ImageManager.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <condition_variable>
-#include <mutex>
-#include <queue>
-#include <thread>
-
-#include <ui/GraphicBuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class ImageManager {
-public:
-    struct Barrier {
-        std::mutex mutex;
-        std::condition_variable_any condition;
-        bool isOpen GUARDED_BY(mutex) = false;
-        status_t result GUARDED_BY(mutex) = NO_ERROR;
-    };
-    ImageManager(GLESRenderEngine* engine);
-    ~ImageManager();
-    // Starts the background thread for the ImageManager
-    // We need this to guarantee that the class is fully-constructed before the
-    // thread begins running.
-    void initThread();
-    void cacheAsync(const sp<GraphicBuffer>& buffer, const std::shared_ptr<Barrier>& barrier)
-            EXCLUDES(mMutex);
-    status_t cache(const sp<GraphicBuffer>& buffer);
-    void releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) EXCLUDES(mMutex);
-
-private:
-    struct QueueEntry {
-        enum class Operation { Delete, Insert };
-
-        Operation op = Operation::Delete;
-        sp<GraphicBuffer> buffer = nullptr;
-        uint64_t bufferId = 0;
-        std::shared_ptr<Barrier> barrier = nullptr;
-    };
-
-    void queueOperation(const QueueEntry&& entry);
-    void threadMain();
-    GLESRenderEngine* const mEngine;
-    std::thread mThread;
-    std::condition_variable_any mCondition;
-    std::mutex mMutex;
-    std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);
-
-    bool mRunning GUARDED_BY(mMutex) = true;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/Program.cpp b/libs/renderengine/gl/Program.cpp
deleted file mode 100644
index 26f6166..0000000
--- a/libs/renderengine/gl/Program.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*Gluint
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "Program.h"
-
-#include <stdint.h>
-
-#include <log/log.h>
-#include <math/mat4.h>
-#include <utils/String8.h>
-#include "ProgramCache.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-Program::Program(const ProgramCache::Key& /*needs*/, const char* vertex, const char* fragment)
-      : mInitialized(false) {
-    GLuint vertexId = buildShader(vertex, GL_VERTEX_SHADER);
-    GLuint fragmentId = buildShader(fragment, GL_FRAGMENT_SHADER);
-    GLuint programId = glCreateProgram();
-    glAttachShader(programId, vertexId);
-    glAttachShader(programId, fragmentId);
-    glBindAttribLocation(programId, position, "position");
-    glBindAttribLocation(programId, texCoords, "texCoords");
-    glBindAttribLocation(programId, cropCoords, "cropCoords");
-    glBindAttribLocation(programId, shadowColor, "shadowColor");
-    glBindAttribLocation(programId, shadowParams, "shadowParams");
-    glLinkProgram(programId);
-
-    GLint status;
-    glGetProgramiv(programId, GL_LINK_STATUS, &status);
-    if (status != GL_TRUE) {
-        ALOGE("Error while linking shaders:");
-        GLint infoLen = 0;
-        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLen);
-        if (infoLen > 1) {
-            GLchar log[infoLen];
-            glGetProgramInfoLog(programId, infoLen, 0, &log[0]);
-            ALOGE("%s", log);
-        }
-        glDetachShader(programId, vertexId);
-        glDetachShader(programId, fragmentId);
-        glDeleteShader(vertexId);
-        glDeleteShader(fragmentId);
-        glDeleteProgram(programId);
-    } else {
-        mProgram = programId;
-        mVertexShader = vertexId;
-        mFragmentShader = fragmentId;
-        mInitialized = true;
-        mProjectionMatrixLoc = glGetUniformLocation(programId, "projection");
-        mTextureMatrixLoc = glGetUniformLocation(programId, "texture");
-        mSamplerLoc = glGetUniformLocation(programId, "sampler");
-        mColorLoc = glGetUniformLocation(programId, "color");
-        mDisplayColorMatrixLoc = glGetUniformLocation(programId, "displayColorMatrix");
-        mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
-        mMaxMasteringLuminanceLoc = glGetUniformLocation(programId, "maxMasteringLuminance");
-        mMaxContentLuminanceLoc = glGetUniformLocation(programId, "maxContentLuminance");
-        mInputTransformMatrixLoc = glGetUniformLocation(programId, "inputTransformMatrix");
-        mOutputTransformMatrixLoc = glGetUniformLocation(programId, "outputTransformMatrix");
-        mCornerRadiusLoc = glGetUniformLocation(programId, "cornerRadius");
-        mCropCenterLoc = glGetUniformLocation(programId, "cropCenter");
-
-        // set-up the default values for our uniforms
-        glUseProgram(programId);
-        glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, mat4().asArray());
-        glEnableVertexAttribArray(0);
-    }
-}
-
-Program::~Program() {
-    glDetachShader(mProgram, mVertexShader);
-    glDetachShader(mProgram, mFragmentShader);
-    glDeleteShader(mVertexShader);
-    glDeleteShader(mFragmentShader);
-    glDeleteProgram(mProgram);
-}
-
-bool Program::isValid() const {
-    return mInitialized;
-}
-
-void Program::use() {
-    glUseProgram(mProgram);
-}
-
-GLuint Program::getAttrib(const char* name) const {
-    // TODO: maybe use a local cache
-    return glGetAttribLocation(mProgram, name);
-}
-
-GLint Program::getUniform(const char* name) const {
-    // TODO: maybe use a local cache
-    return glGetUniformLocation(mProgram, name);
-}
-
-GLuint Program::buildShader(const char* source, GLenum type) {
-    GLuint shader = glCreateShader(type);
-    glShaderSource(shader, 1, &source, 0);
-    glCompileShader(shader);
-    GLint status;
-    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
-    if (status != GL_TRUE) {
-        // Some drivers return wrong values for GL_INFO_LOG_LENGTH
-        // use a fixed size instead
-        GLchar log[512];
-        glGetShaderInfoLog(shader, sizeof(log), 0, log);
-        ALOGE("Error while compiling shader: \n%s\n%s", source, log);
-        glDeleteShader(shader);
-        return 0;
-    }
-    return shader;
-}
-
-void Program::setUniforms(const Description& desc) {
-    // TODO: we should have a mechanism here to not always reset uniforms that
-    // didn't change for this program.
-
-    if (mSamplerLoc >= 0) {
-        glUniform1i(mSamplerLoc, 0);
-        glUniformMatrix4fv(mTextureMatrixLoc, 1, GL_FALSE, desc.texture.getMatrix().asArray());
-    }
-    if (mColorLoc >= 0) {
-        const float color[4] = {desc.color.r, desc.color.g, desc.color.b, desc.color.a};
-        glUniform4fv(mColorLoc, 1, color);
-    }
-    if (mDisplayColorMatrixLoc >= 0) {
-        glUniformMatrix4fv(mDisplayColorMatrixLoc, 1, GL_FALSE, desc.displayColorMatrix.asArray());
-    }
-    if (mInputTransformMatrixLoc >= 0) {
-        mat4 inputTransformMatrix = desc.inputTransformMatrix;
-        glUniformMatrix4fv(mInputTransformMatrixLoc, 1, GL_FALSE, inputTransformMatrix.asArray());
-    }
-    if (mOutputTransformMatrixLoc >= 0) {
-        // The output transform matrix and color matrix can be combined as one matrix
-        // that is applied right before applying OETF.
-        mat4 outputTransformMatrix = desc.colorMatrix * desc.outputTransformMatrix;
-        glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE, outputTransformMatrix.asArray());
-    }
-    if (mDisplayMaxLuminanceLoc >= 0) {
-        glUniform1f(mDisplayMaxLuminanceLoc, desc.displayMaxLuminance);
-    }
-    if (mMaxMasteringLuminanceLoc >= 0) {
-        glUniform1f(mMaxMasteringLuminanceLoc, desc.maxMasteringLuminance);
-    }
-    if (mMaxContentLuminanceLoc >= 0) {
-        glUniform1f(mMaxContentLuminanceLoc, desc.maxContentLuminance);
-    }
-    if (mCornerRadiusLoc >= 0) {
-        glUniform1f(mCornerRadiusLoc, desc.cornerRadius);
-    }
-    if (mCropCenterLoc >= 0) {
-        glUniform2f(mCropCenterLoc, desc.cropSize.x / 2.0f, desc.cropSize.y / 2.0f);
-    }
-    // these uniforms are always present
-    glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, desc.projectionMatrix.asArray());
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/Program.h b/libs/renderengine/gl/Program.h
deleted file mode 100644
index 41f1bf8..0000000
--- a/libs/renderengine/gl/Program.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 SF_RENDER_ENGINE_PROGRAM_H
-#define SF_RENDER_ENGINE_PROGRAM_H
-
-#include <stdint.h>
-
-#include <GLES2/gl2.h>
-#include <renderengine/private/Description.h>
-#include "ProgramCache.h"
-
-namespace android {
-
-class String8;
-
-namespace renderengine {
-namespace gl {
-
-/*
- * Abstracts a GLSL program comprising a vertex and fragment shader
- */
-class Program {
-public:
-    // known locations for position and texture coordinates
-    enum {
-        /* position of each vertex for vertex shader */
-        position = 0,
-
-        /* UV coordinates for texture mapping */
-        texCoords = 1,
-
-        /* Crop coordinates, in pixels */
-        cropCoords = 2,
-
-        /* Shadow color */
-        shadowColor = 3,
-
-        /* Shadow params */
-        shadowParams = 4,
-    };
-
-    Program(const ProgramCache::Key& needs, const char* vertex, const char* fragment);
-    ~Program();
-
-    /* whether this object is usable */
-    bool isValid() const;
-
-    /* Binds this program to the GLES context */
-    void use();
-
-    /* Returns the location of the specified attribute */
-    GLuint getAttrib(const char* name) const;
-
-    /* Returns the location of the specified uniform */
-    GLint getUniform(const char* name) const;
-
-    /* set-up uniforms from the description */
-    void setUniforms(const Description& desc);
-
-private:
-    GLuint buildShader(const char* source, GLenum type);
-
-    // whether the initialization succeeded
-    bool mInitialized;
-
-    // Name of the OpenGL program and shaders
-    GLuint mProgram;
-    GLuint mVertexShader;
-    GLuint mFragmentShader;
-
-    /* location of the projection matrix uniform */
-    GLint mProjectionMatrixLoc;
-
-    /* location of the texture matrix uniform */
-    GLint mTextureMatrixLoc;
-
-    /* location of the sampler uniform */
-    GLint mSamplerLoc;
-
-    /* location of the color uniform */
-    GLint mColorLoc;
-
-    /* location of display luminance uniform */
-    GLint mDisplayMaxLuminanceLoc;
-    /* location of max mastering luminance uniform */
-    GLint mMaxMasteringLuminanceLoc;
-    /* location of max content luminance uniform */
-    GLint mMaxContentLuminanceLoc;
-
-    /* location of transform matrix */
-    GLint mInputTransformMatrixLoc;
-    GLint mOutputTransformMatrixLoc;
-    GLint mDisplayColorMatrixLoc;
-
-    /* location of corner radius uniform */
-    GLint mCornerRadiusLoc;
-
-    /* location of surface crop origin uniform, for rounded corner clipping */
-    GLint mCropCenterLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_PROGRAM_H */
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
deleted file mode 100644
index 422b070..0000000
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ /dev/null
@@ -1,830 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "ProgramCache.h"
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <log/log.h>
-#include <renderengine/private/Description.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-#include "Program.h"
-
-ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::gl::ProgramCache)
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/*
- * A simple formatter class to automatically add the endl and
- * manage the indentation.
- */
-
-class Formatter;
-static Formatter& indent(Formatter& f);
-static Formatter& dedent(Formatter& f);
-
-class Formatter {
-    String8 mString;
-    int mIndent;
-    typedef Formatter& (*FormaterManipFunc)(Formatter&);
-    friend Formatter& indent(Formatter& f);
-    friend Formatter& dedent(Formatter& f);
-
-public:
-    Formatter() : mIndent(0) {}
-
-    String8 getString() const { return mString; }
-
-    friend Formatter& operator<<(Formatter& out, const char* in) {
-        for (int i = 0; i < out.mIndent; i++) {
-            out.mString.append("    ");
-        }
-        out.mString.append(in);
-        out.mString.append("\n");
-        return out;
-    }
-    friend inline Formatter& operator<<(Formatter& out, const String8& in) {
-        return operator<<(out, in.c_str());
-    }
-    friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
-        return (*func)(to);
-    }
-};
-Formatter& indent(Formatter& f) {
-    f.mIndent++;
-    return f;
-}
-Formatter& dedent(Formatter& f) {
-    f.mIndent--;
-    return f;
-}
-
-void ProgramCache::primeCache(
-        EGLContext context, bool useColorManagement, bool toneMapperShaderOnly) {
-    auto& cache = mCaches[context];
-    uint32_t shaderCount = 0;
-
-    if (toneMapperShaderOnly) {
-        Key shaderKey;
-        // base settings used by HDR->SDR tonemap only
-        shaderKey.set(Key::BLEND_MASK | Key::INPUT_TRANSFORM_MATRIX_MASK |
-                      Key::OUTPUT_TRANSFORM_MATRIX_MASK | Key::OUTPUT_TF_MASK |
-                      Key::OPACITY_MASK | Key::ALPHA_MASK |
-                      Key::ROUNDED_CORNERS_MASK | Key::TEXTURE_MASK,
-                      Key::BLEND_NORMAL | Key::INPUT_TRANSFORM_MATRIX_ON |
-                      Key::OUTPUT_TRANSFORM_MATRIX_ON | Key::OUTPUT_TF_SRGB |
-                      Key::OPACITY_OPAQUE | Key::ALPHA_EQ_ONE |
-                      Key::ROUNDED_CORNERS_OFF | Key::TEXTURE_EXT);
-        for (int i = 0; i < 4; i++) {
-            // Cache input transfer for HLG & ST2084
-            shaderKey.set(Key::INPUT_TF_MASK, (i & 1) ?
-                    Key::INPUT_TF_HLG : Key::INPUT_TF_ST2084);
-
-            // Cache Y410 input on or off
-            shaderKey.set(Key::Y410_BT2020_MASK, (i & 2) ?
-                    Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
-            if (cache.count(shaderKey) == 0) {
-                cache.emplace(shaderKey, generateProgram(shaderKey));
-                shaderCount++;
-            }
-        }
-        return;
-    }
-
-    uint32_t keyMask = Key::BLEND_MASK | Key::OPACITY_MASK | Key::ALPHA_MASK | Key::TEXTURE_MASK
-        | Key::ROUNDED_CORNERS_MASK;
-    // Prime the cache for all combinations of the above masks,
-    // leaving off the experimental color matrix mask options.
-
-    nsecs_t timeBefore = systemTime();
-    for (uint32_t keyVal = 0; keyVal <= keyMask; keyVal++) {
-        Key shaderKey;
-        shaderKey.set(keyMask, keyVal);
-        uint32_t tex = shaderKey.getTextureTarget();
-        if (tex != Key::TEXTURE_OFF && tex != Key::TEXTURE_EXT && tex != Key::TEXTURE_2D) {
-            continue;
-        }
-        if (cache.count(shaderKey) == 0) {
-            cache.emplace(shaderKey, generateProgram(shaderKey));
-            shaderCount++;
-        }
-    }
-
-    // Prime for sRGB->P3 conversion
-    if (useColorManagement) {
-        Key shaderKey;
-        shaderKey.set(Key::BLEND_MASK | Key::OUTPUT_TRANSFORM_MATRIX_MASK | Key::INPUT_TF_MASK |
-                              Key::OUTPUT_TF_MASK,
-                      Key::BLEND_PREMULT | Key::OUTPUT_TRANSFORM_MATRIX_ON | Key::INPUT_TF_SRGB |
-                              Key::OUTPUT_TF_SRGB);
-        for (int i = 0; i < 16; i++) {
-            shaderKey.set(Key::OPACITY_MASK,
-                          (i & 1) ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT);
-            shaderKey.set(Key::ALPHA_MASK, (i & 2) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE);
-
-            // Cache rounded corners
-            shaderKey.set(Key::ROUNDED_CORNERS_MASK,
-                          (i & 4) ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF);
-
-            // Cache texture off option for window transition
-            shaderKey.set(Key::TEXTURE_MASK, (i & 8) ? Key::TEXTURE_EXT : Key::TEXTURE_OFF);
-            if (cache.count(shaderKey) == 0) {
-                cache.emplace(shaderKey, generateProgram(shaderKey));
-                shaderCount++;
-            }
-        }
-    }
-
-    nsecs_t timeAfter = systemTime();
-    float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
-    ALOGD("shader cache generated - %u shaders in %f ms\n", shaderCount, compileTimeMs);
-}
-
-ProgramCache::Key ProgramCache::computeKey(const Description& description) {
-    Key needs;
-    needs.set(Key::TEXTURE_MASK,
-              !description.textureEnabled
-                      ? Key::TEXTURE_OFF
-                      : description.texture.getTextureTarget() == GL_TEXTURE_EXTERNAL_OES
-                              ? Key::TEXTURE_EXT
-                              : description.texture.getTextureTarget() == GL_TEXTURE_2D
-                                      ? Key::TEXTURE_2D
-                                      : Key::TEXTURE_OFF)
-            .set(Key::ALPHA_MASK, (description.color.a < 1) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE)
-            .set(Key::BLEND_MASK,
-                 description.isPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
-            .set(Key::OPACITY_MASK,
-                 description.isOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
-            .set(Key::Key::INPUT_TRANSFORM_MATRIX_MASK,
-                 description.hasInputTransformMatrix() ? Key::INPUT_TRANSFORM_MATRIX_ON
-                                                       : Key::INPUT_TRANSFORM_MATRIX_OFF)
-            .set(Key::Key::OUTPUT_TRANSFORM_MATRIX_MASK,
-                 description.hasOutputTransformMatrix() || description.hasColorMatrix()
-                         ? Key::OUTPUT_TRANSFORM_MATRIX_ON
-                         : Key::OUTPUT_TRANSFORM_MATRIX_OFF)
-            .set(Key::Key::DISPLAY_COLOR_TRANSFORM_MATRIX_MASK,
-                 description.hasDisplayColorMatrix() ? Key::DISPLAY_COLOR_TRANSFORM_MATRIX_ON
-                                                     : Key::DISPLAY_COLOR_TRANSFORM_MATRIX_OFF)
-            .set(Key::ROUNDED_CORNERS_MASK,
-                 description.cornerRadius > 0 ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF)
-            .set(Key::SHADOW_MASK, description.drawShadows ? Key::SHADOW_ON : Key::SHADOW_OFF);
-    needs.set(Key::Y410_BT2020_MASK,
-              description.isY410BT2020 ? Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
-
-    if (needs.hasTransformMatrix() ||
-        (description.inputTransferFunction != description.outputTransferFunction)) {
-        switch (description.inputTransferFunction) {
-            case Description::TransferFunction::LINEAR:
-            default:
-                needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_LINEAR);
-                break;
-            case Description::TransferFunction::SRGB:
-                needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_SRGB);
-                break;
-            case Description::TransferFunction::ST2084:
-                needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_ST2084);
-                break;
-            case Description::TransferFunction::HLG:
-                needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_HLG);
-                break;
-        }
-
-        switch (description.outputTransferFunction) {
-            case Description::TransferFunction::LINEAR:
-            default:
-                needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_LINEAR);
-                break;
-            case Description::TransferFunction::SRGB:
-                needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_SRGB);
-                break;
-            case Description::TransferFunction::ST2084:
-                needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_ST2084);
-                break;
-            case Description::TransferFunction::HLG:
-                needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_HLG);
-                break;
-        }
-    }
-
-    return needs;
-}
-
-// Generate EOTF that converts signal values to relative display light,
-// both normalized to [0, 1].
-void ProgramCache::generateEOTF(Formatter& fs, const Key& needs) {
-    switch (needs.getInputTF()) {
-        case Key::INPUT_TF_SRGB:
-            fs << R"__SHADER__(
-                float EOTF_sRGB(float srgb) {
-                    return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
-                }
-
-                vec3 EOTF_sRGB(const vec3 srgb) {
-                    return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                vec3 EOTF(const vec3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )__SHADER__";
-            break;
-        case Key::INPUT_TF_ST2084:
-            fs << R"__SHADER__(
-                vec3 EOTF(const highp vec3 color) {
-                    const highp float m1 = (2610.0 / 4096.0) / 4.0;
-                    const highp float m2 = (2523.0 / 4096.0) * 128.0;
-                    const highp float c1 = (3424.0 / 4096.0);
-                    const highp float c2 = (2413.0 / 4096.0) * 32.0;
-                    const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
-                    highp vec3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / vec3(m2));
-                    tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
-                    return pow(tmp, 1.0 / vec3(m1));
-                }
-            )__SHADER__";
-            break;
-        case Key::INPUT_TF_HLG:
-            fs << R"__SHADER__(
-                highp float EOTF_channel(const highp float channel) {
-                    const highp float a = 0.17883277;
-                    const highp float b = 0.28466892;
-                    const highp float c = 0.55991073;
-                    return channel <= 0.5 ? channel * channel / 3.0 :
-                            (exp((channel - c) / a) + b) / 12.0;
-                }
-
-                vec3 EOTF(const highp vec3 color) {
-                    return vec3(EOTF_channel(color.r), EOTF_channel(color.g),
-                            EOTF_channel(color.b));
-                }
-            )__SHADER__";
-            break;
-        default:
-            fs << R"__SHADER__(
-                vec3 EOTF(const vec3 linear) {
-                    return linear;
-                }
-            )__SHADER__";
-            break;
-    }
-}
-
-void ProgramCache::generateToneMappingProcess(Formatter& fs, const Key& needs) {
-    // Convert relative light to absolute light.
-    switch (needs.getInputTF()) {
-        case Key::INPUT_TF_ST2084:
-            fs << R"__SHADER__(
-                highp vec3 ScaleLuminance(highp vec3 color) {
-                    return color * 10000.0;
-                }
-            )__SHADER__";
-            break;
-        case Key::INPUT_TF_HLG:
-            fs << R"__SHADER__(
-                highp vec3 ScaleLuminance(highp vec3 color) {
-                    // The formula is:
-                    // alpha * pow(Y, gamma - 1.0) * color + beta;
-                    // where alpha is 1000.0, gamma is 1.2, beta is 0.0.
-                    return color * 1000.0 * pow(color.y, 0.2);
-                }
-            )__SHADER__";
-            break;
-        default:
-            fs << R"__SHADER__(
-                highp vec3 ScaleLuminance(highp vec3 color) {
-                    return color * displayMaxLuminance;
-                }
-            )__SHADER__";
-            break;
-    }
-
-    // Tone map absolute light to display luminance range.
-    switch (needs.getInputTF()) {
-        case Key::INPUT_TF_ST2084:
-        case Key::INPUT_TF_HLG:
-            switch (needs.getOutputTF()) {
-                case Key::OUTPUT_TF_HLG:
-                    // Right now when mixed PQ and HLG contents are presented,
-                    // HLG content will always be converted to PQ. However, for
-                    // completeness, we simply clamp the value to [0.0, 1000.0].
-                    fs << R"__SHADER__(
-                        highp vec3 ToneMap(highp vec3 color) {
-                            return clamp(color, 0.0, 1000.0);
-                        }
-                    )__SHADER__";
-                    break;
-                case Key::OUTPUT_TF_ST2084:
-                    fs << R"__SHADER__(
-                        highp vec3 ToneMap(highp vec3 color) {
-                            return color;
-                        }
-                    )__SHADER__";
-                    break;
-                default:
-                    fs << R"__SHADER__(
-                        highp vec3 ToneMap(highp vec3 color) {
-                            float maxMasteringLumi = maxMasteringLuminance;
-                            float maxContentLumi = maxContentLuminance;
-                            float maxInLumi = min(maxMasteringLumi, maxContentLumi);
-                            float maxOutLumi = displayMaxLuminance;
-
-                            float nits = color.y;
-
-                            // clamp to max input luminance
-                            nits = clamp(nits, 0.0, maxInLumi);
-
-                            // scale [0.0, maxInLumi] to [0.0, maxOutLumi]
-                            if (maxInLumi <= maxOutLumi) {
-                                return color * (maxOutLumi / maxInLumi);
-                            } else {
-                                // three control points
-                                const float x0 = 10.0;
-                                const float y0 = 17.0;
-                                float x1 = maxOutLumi * 0.75;
-                                float y1 = x1;
-                                float x2 = x1 + (maxInLumi - x1) / 2.0;
-                                float y2 = y1 + (maxOutLumi - y1) * 0.75;
-
-                                // horizontal distances between the last three control points
-                                float h12 = x2 - x1;
-                                float h23 = maxInLumi - x2;
-                                // tangents at the last three control points
-                                float m1 = (y2 - y1) / h12;
-                                float m3 = (maxOutLumi - y2) / h23;
-                                float m2 = (m1 + m3) / 2.0;
-
-                                if (nits < x0) {
-                                    // scale [0.0, x0] to [0.0, y0] linearly
-                                    float slope = y0 / x0;
-                                    return color * slope;
-                                } else if (nits < x1) {
-                                    // scale [x0, x1] to [y0, y1] linearly
-                                    float slope = (y1 - y0) / (x1 - x0);
-                                    nits = y0 + (nits - x0) * slope;
-                                } else if (nits < x2) {
-                                    // scale [x1, x2] to [y1, y2] using Hermite interp
-                                    float t = (nits - x1) / h12;
-                                    nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
-                                            (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
-                                } else {
-                                    // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
-                                    float t = (nits - x2) / h23;
-                                    nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
-                                            (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
-                                }
-                            }
-
-                            // color.y is greater than x0 and is thus non-zero
-                            return color * (nits / color.y);
-                        }
-                    )__SHADER__";
-                    break;
-            }
-            break;
-        default:
-            // inverse tone map; the output luminance can be up to maxOutLumi.
-            fs << R"__SHADER__(
-                highp vec3 ToneMap(highp vec3 color) {
-                    const float maxOutLumi = 3000.0;
-
-                    const float x0 = 5.0;
-                    const float y0 = 2.5;
-                    float x1 = displayMaxLuminance * 0.7;
-                    float y1 = maxOutLumi * 0.15;
-                    float x2 = displayMaxLuminance * 0.9;
-                    float y2 = maxOutLumi * 0.45;
-                    float x3 = displayMaxLuminance;
-                    float y3 = maxOutLumi;
-
-                    float c1 = y1 / 3.0;
-                    float c2 = y2 / 2.0;
-                    float c3 = y3 / 1.5;
-
-                    float nits = color.y;
-
-                    float scale;
-                    if (nits <= x0) {
-                        // scale [0.0, x0] to [0.0, y0] linearly
-                        const float slope = y0 / x0;
-                        return color * slope;
-                    } else if (nits <= x1) {
-                        // scale [x0, x1] to [y0, y1] using a curve
-                        float t = (nits - x0) / (x1 - x0);
-                        nits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + t * t * y1;
-                    } else if (nits <= x2) {
-                        // scale [x1, x2] to [y1, y2] using a curve
-                        float t = (nits - x1) / (x2 - x1);
-                        nits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + t * t * y2;
-                    } else {
-                        // scale [x2, x3] to [y2, y3] using a curve
-                        float t = (nits - x2) / (x3 - x2);
-                        nits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3;
-                    }
-
-                    // color.y is greater than x0 and is thus non-zero
-                    return color * (nits / color.y);
-                }
-            )__SHADER__";
-            break;
-    }
-
-    // convert absolute light to relative light.
-    switch (needs.getOutputTF()) {
-        case Key::OUTPUT_TF_ST2084:
-            fs << R"__SHADER__(
-                highp vec3 NormalizeLuminance(highp vec3 color) {
-                    return color / 10000.0;
-                }
-            )__SHADER__";
-            break;
-        case Key::OUTPUT_TF_HLG:
-            fs << R"__SHADER__(
-                highp vec3 NormalizeLuminance(highp vec3 color) {
-                    return color / 1000.0 * pow(color.y / 1000.0, -0.2 / 1.2);
-                }
-            )__SHADER__";
-            break;
-        default:
-            fs << R"__SHADER__(
-                highp vec3 NormalizeLuminance(highp vec3 color) {
-                    return color / displayMaxLuminance;
-                }
-            )__SHADER__";
-            break;
-    }
-}
-
-// Generate OOTF that modifies the relative scence light to relative display light.
-void ProgramCache::generateOOTF(Formatter& fs, const ProgramCache::Key& needs) {
-    if (!needs.needsToneMapping()) {
-        fs << R"__SHADER__(
-            highp vec3 OOTF(const highp vec3 color) {
-                return color;
-            }
-        )__SHADER__";
-    } else {
-        generateToneMappingProcess(fs, needs);
-        fs << R"__SHADER__(
-            highp vec3 OOTF(const highp vec3 color) {
-                return NormalizeLuminance(ToneMap(ScaleLuminance(color)));
-            }
-        )__SHADER__";
-    }
-}
-
-// Generate OETF that converts relative display light to signal values,
-// both normalized to [0, 1]
-void ProgramCache::generateOETF(Formatter& fs, const Key& needs) {
-    switch (needs.getOutputTF()) {
-        case Key::OUTPUT_TF_SRGB:
-            fs << R"__SHADER__(
-                float OETF_sRGB(const float linear) {
-                    return linear <= 0.0031308 ?
-                            linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
-                }
-
-                vec3 OETF_sRGB(const vec3 linear) {
-                    return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                vec3 OETF(const vec3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )__SHADER__";
-            break;
-        case Key::OUTPUT_TF_ST2084:
-            fs << R"__SHADER__(
-                vec3 OETF(const vec3 linear) {
-                    const highp float m1 = (2610.0 / 4096.0) / 4.0;
-                    const highp float m2 = (2523.0 / 4096.0) * 128.0;
-                    const highp float c1 = (3424.0 / 4096.0);
-                    const highp float c2 = (2413.0 / 4096.0) * 32.0;
-                    const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
-                    highp vec3 tmp = pow(linear, vec3(m1));
-                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
-                    return pow(tmp, vec3(m2));
-                }
-            )__SHADER__";
-            break;
-        case Key::OUTPUT_TF_HLG:
-            fs << R"__SHADER__(
-                highp float OETF_channel(const highp float channel) {
-                    const highp float a = 0.17883277;
-                    const highp float b = 0.28466892;
-                    const highp float c = 0.55991073;
-                    return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
-                            a * log(12.0 * channel - b) + c;
-                }
-
-                vec3 OETF(const highp vec3 color) {
-                    return vec3(OETF_channel(color.r), OETF_channel(color.g),
-                            OETF_channel(color.b));
-                }
-            )__SHADER__";
-            break;
-        default:
-            fs << R"__SHADER__(
-                vec3 OETF(const vec3 linear) {
-                    return linear;
-                }
-            )__SHADER__";
-            break;
-    }
-}
-
-String8 ProgramCache::generateVertexShader(const Key& needs) {
-    Formatter vs;
-    if (needs.hasTextureCoords()) {
-        vs << "attribute vec4 texCoords;"
-           << "varying vec2 outTexCoords;";
-    }
-    if (needs.hasRoundedCorners()) {
-        vs << "attribute lowp vec4 cropCoords;";
-        vs << "varying lowp vec2 outCropCoords;";
-    }
-    if (needs.drawShadows()) {
-        vs << "attribute lowp vec4 shadowColor;";
-        vs << "varying lowp vec4 outShadowColor;";
-        vs << "attribute lowp vec4 shadowParams;";
-        vs << "varying lowp vec3 outShadowParams;";
-    }
-    vs << "attribute vec4 position;"
-       << "uniform mat4 projection;"
-       << "uniform mat4 texture;"
-       << "void main(void) {" << indent << "gl_Position = projection * position;";
-    if (needs.hasTextureCoords()) {
-        vs << "outTexCoords = (texture * texCoords).st;";
-    }
-    if (needs.hasRoundedCorners()) {
-        vs << "outCropCoords = cropCoords.st;";
-    }
-    if (needs.drawShadows()) {
-        vs << "outShadowColor = shadowColor;";
-        vs << "outShadowParams = shadowParams.xyz;";
-    }
-    vs << dedent << "}";
-    return vs.getString();
-}
-
-String8 ProgramCache::generateFragmentShader(const Key& needs) {
-    Formatter fs;
-    if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
-        fs << "#extension GL_OES_EGL_image_external : require";
-    }
-
-    // default precision is required-ish in fragment shaders
-    fs << "precision mediump float;";
-
-    if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
-        fs << "uniform samplerExternalOES sampler;";
-    } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
-        fs << "uniform sampler2D sampler;";
-    }
-
-    if (needs.hasTextureCoords()) {
-        fs << "varying highp vec2 outTexCoords;";
-    }
-
-    if (needs.hasRoundedCorners()) {
-        // Rounded corners implementation using a signed distance function.
-        fs << R"__SHADER__(
-            uniform float cornerRadius;
-            uniform vec2 cropCenter;
-            varying vec2 outCropCoords;
-
-            /**
-             * This function takes the current crop coordinates and calculates an alpha value based
-             * on the corner radius and distance from the crop center.
-             */
-            float applyCornerRadius(vec2 cropCoords)
-            {
-                vec2 position = cropCoords - cropCenter;
-                // Scale down the dist vector here, as otherwise large corner
-                // radii can cause floating point issues when computing the norm
-                vec2 dist = (abs(position) - cropCenter + vec2(cornerRadius)) / 16.0;
-                // Once we've found the norm, then scale back up.
-                float plane = length(max(dist, vec2(0.0))) * 16.0;
-                return 1.0 - clamp(plane - cornerRadius, 0.0, 1.0);
-            }
-            )__SHADER__";
-    }
-
-    if (needs.drawShadows()) {
-        fs << R"__SHADER__(
-            varying lowp vec4 outShadowColor;
-            varying lowp vec3 outShadowParams;
-
-            /**
-             * Returns the shadow color.
-             */
-            vec4 getShadowColor()
-            {
-                lowp float d = length(outShadowParams.xy);
-                vec2 uv = vec2(outShadowParams.z * (1.0 - d), 0.5);
-                lowp float factor = texture2D(sampler, uv).a;
-                return outShadowColor * factor;
-            }
-            )__SHADER__";
-    }
-
-    if (needs.getTextureTarget() == Key::TEXTURE_OFF || needs.hasAlpha()) {
-        fs << "uniform vec4 color;";
-    }
-
-    if (needs.isY410BT2020()) {
-        fs << R"__SHADER__(
-            vec3 convertY410BT2020(const vec3 color) {
-                const vec3 offset = vec3(0.0625, 0.5, 0.5);
-                const mat3 transform = mat3(
-                    vec3(1.1678,  1.1678, 1.1678),
-                    vec3(   0.0, -0.1878, 2.1481),
-                    vec3(1.6836, -0.6523,   0.0));
-                // Y is in G, U is in R, and V is in B
-                return clamp(transform * (color.grb - offset), 0.0, 1.0);
-            }
-            )__SHADER__";
-    }
-
-    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
-        needs.hasDisplayColorMatrix()) {
-        if (needs.needsToneMapping()) {
-            fs << "uniform float displayMaxLuminance;";
-            fs << "uniform float maxMasteringLuminance;";
-            fs << "uniform float maxContentLuminance;";
-        }
-
-        if (needs.hasInputTransformMatrix()) {
-            fs << "uniform mat4 inputTransformMatrix;";
-            fs << R"__SHADER__(
-                highp vec3 InputTransform(const highp vec3 color) {
-                    return clamp(vec3(inputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
-                }
-            )__SHADER__";
-        } else {
-            fs << R"__SHADER__(
-                highp vec3 InputTransform(const highp vec3 color) {
-                    return color;
-                }
-            )__SHADER__";
-        }
-
-        // the transformation from a wider colorspace to a narrower one can
-        // result in >1.0 or <0.0 pixel values
-        if (needs.hasOutputTransformMatrix()) {
-            fs << "uniform mat4 outputTransformMatrix;";
-            fs << R"__SHADER__(
-                highp vec3 OutputTransform(const highp vec3 color) {
-                    return clamp(vec3(outputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
-                }
-            )__SHADER__";
-        } else {
-            fs << R"__SHADER__(
-                highp vec3 OutputTransform(const highp vec3 color) {
-                    return clamp(color, 0.0, 1.0);
-                }
-            )__SHADER__";
-        }
-
-        if (needs.hasDisplayColorMatrix()) {
-            fs << "uniform mat4 displayColorMatrix;";
-            fs << R"__SHADER__(
-                highp vec3 DisplayColorMatrix(const highp vec3 color) {
-                    return clamp(vec3(displayColorMatrix * vec4(color, 1.0)), 0.0, 1.0);
-                }
-            )__SHADER__";
-        } else {
-            fs << R"__SHADER__(
-                highp vec3 DisplayColorMatrix(const highp vec3 color) {
-                    return color;
-                }
-            )__SHADER__";
-        }
-
-        generateEOTF(fs, needs);
-        generateOOTF(fs, needs);
-        generateOETF(fs, needs);
-    }
-
-    fs << "void main(void) {" << indent;
-    if (needs.drawShadows()) {
-        fs << "gl_FragColor = getShadowColor();";
-    } else {
-        if (needs.isTexturing()) {
-            fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
-            if (needs.isY410BT2020()) {
-                fs << "gl_FragColor.rgb = convertY410BT2020(gl_FragColor.rgb);";
-            }
-        } else {
-            fs << "gl_FragColor.rgb = color.rgb;";
-            fs << "gl_FragColor.a = 1.0;";
-        }
-        if (needs.isOpaque()) {
-            fs << "gl_FragColor.a = 1.0;";
-        }
-    }
-
-    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
-        needs.hasDisplayColorMatrix()) {
-        if (!needs.isOpaque() && needs.isPremultiplied()) {
-            // un-premultiply if needed before linearization
-            // avoid divide by 0 by adding 0.5/256 to the alpha channel
-            fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
-        }
-        fs << "gl_FragColor.rgb = "
-              "DisplayColorMatrix(OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))"
-              ")));";
-
-        if (!needs.isOpaque() && needs.isPremultiplied()) {
-            // and re-premultiply if needed after gamma correction
-            fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
-        }
-    }
-
-    /*
-     * Whether applying layer alpha before or after color transform doesn't matter,
-     * as long as we can undo premultiplication. But we cannot un-premultiply
-     * for color transform if the layer alpha = 0, e.g. 0 / (0 + 0.0019) = 0.
-     */
-    if (!needs.drawShadows()) {
-        if (needs.hasAlpha()) {
-            // modulate the current alpha value with alpha set
-            if (needs.isPremultiplied()) {
-                // ... and the color too if we're premultiplied
-                fs << "gl_FragColor *= color.a;";
-            } else {
-                fs << "gl_FragColor.a *= color.a;";
-            }
-        }
-    }
-
-    if (needs.hasRoundedCorners()) {
-        if (needs.isPremultiplied()) {
-            fs << "gl_FragColor *= vec4(applyCornerRadius(outCropCoords));";
-        } else {
-            fs << "gl_FragColor.a *= applyCornerRadius(outCropCoords);";
-        }
-    }
-
-    fs << dedent << "}";
-    return fs.getString();
-}
-
-std::unique_ptr<Program> ProgramCache::generateProgram(const Key& needs) {
-    ATRACE_CALL();
-
-    // vertex shader
-    String8 vs = generateVertexShader(needs);
-
-    // fragment shader
-    String8 fs = generateFragmentShader(needs);
-
-    return std::make_unique<Program>(needs, vs.c_str(), fs.c_str());
-}
-
-void ProgramCache::useProgram(EGLContext context, const Description& description) {
-    // generate the key for the shader based on the description
-    Key needs(computeKey(description));
-
-    // look-up the program in the cache
-    auto& cache = mCaches[context];
-    auto it = cache.find(needs);
-    if (it == cache.end()) {
-        // we didn't find our program, so generate one...
-        nsecs_t time = systemTime();
-        it = cache.emplace(needs, generateProgram(needs)).first;
-        time = systemTime() - time;
-
-        ALOGV(">>> generated new program for context %p: needs=%08X, time=%u ms (%zu programs)",
-              context, needs.mKey, uint32_t(ns2ms(time)), cache.size());
-    }
-
-    // here we have a suitable program for this description
-    std::unique_ptr<Program>& program = it->second;
-    if (program->isValid()) {
-        program->use();
-        program->setUniforms(description);
-    }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ProgramCache.h b/libs/renderengine/gl/ProgramCache.h
deleted file mode 100644
index 535d21c..0000000
--- a/libs/renderengine/gl/ProgramCache.h
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 SF_RENDER_ENGINE_PROGRAMCACHE_H
-#define SF_RENDER_ENGINE_PROGRAMCACHE_H
-
-#include <memory>
-#include <unordered_map>
-
-#include <EGL/egl.h>
-#include <GLES2/gl2.h>
-#include <renderengine/private/Description.h>
-#include <utils/Singleton.h>
-#include <utils/TypeHelpers.h>
-
-namespace android {
-
-class String8;
-
-namespace renderengine {
-
-struct Description;
-
-namespace gl {
-
-class Formatter;
-class Program;
-
-/*
- * This class generates GLSL programs suitable to handle a given
- * Description. It's responsible for figuring out what to
- * generate from a Description.
- * It also maintains a cache of these Programs.
- */
-class ProgramCache : public Singleton<ProgramCache> {
-public:
-    /*
-     * Key is used to retrieve a Program in the cache.
-     * A Key is generated from a Description.
-     */
-    class Key {
-        friend class ProgramCache;
-        typedef uint32_t key_t;
-        key_t mKey;
-
-    public:
-        enum {
-            BLEND_SHIFT = 0,
-            BLEND_MASK = 1 << BLEND_SHIFT,
-            BLEND_PREMULT = 1 << BLEND_SHIFT,
-            BLEND_NORMAL = 0 << BLEND_SHIFT,
-
-            OPACITY_SHIFT = 1,
-            OPACITY_MASK = 1 << OPACITY_SHIFT,
-            OPACITY_OPAQUE = 1 << OPACITY_SHIFT,
-            OPACITY_TRANSLUCENT = 0 << OPACITY_SHIFT,
-
-            ALPHA_SHIFT = 2,
-            ALPHA_MASK = 1 << ALPHA_SHIFT,
-            ALPHA_LT_ONE = 1 << ALPHA_SHIFT,
-            ALPHA_EQ_ONE = 0 << ALPHA_SHIFT,
-
-            TEXTURE_SHIFT = 3,
-            TEXTURE_MASK = 3 << TEXTURE_SHIFT,
-            TEXTURE_OFF = 0 << TEXTURE_SHIFT,
-            TEXTURE_EXT = 1 << TEXTURE_SHIFT,
-            TEXTURE_2D = 2 << TEXTURE_SHIFT,
-
-            ROUNDED_CORNERS_SHIFT = 5,
-            ROUNDED_CORNERS_MASK = 1 << ROUNDED_CORNERS_SHIFT,
-            ROUNDED_CORNERS_OFF = 0 << ROUNDED_CORNERS_SHIFT,
-            ROUNDED_CORNERS_ON = 1 << ROUNDED_CORNERS_SHIFT,
-
-            INPUT_TRANSFORM_MATRIX_SHIFT = 6,
-            INPUT_TRANSFORM_MATRIX_MASK = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
-            INPUT_TRANSFORM_MATRIX_OFF = 0 << INPUT_TRANSFORM_MATRIX_SHIFT,
-            INPUT_TRANSFORM_MATRIX_ON = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
-
-            OUTPUT_TRANSFORM_MATRIX_SHIFT = 7,
-            OUTPUT_TRANSFORM_MATRIX_MASK = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
-            OUTPUT_TRANSFORM_MATRIX_OFF = 0 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
-            OUTPUT_TRANSFORM_MATRIX_ON = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
-
-            INPUT_TF_SHIFT = 8,
-            INPUT_TF_MASK = 3 << INPUT_TF_SHIFT,
-            INPUT_TF_LINEAR = 0 << INPUT_TF_SHIFT,
-            INPUT_TF_SRGB = 1 << INPUT_TF_SHIFT,
-            INPUT_TF_ST2084 = 2 << INPUT_TF_SHIFT,
-            INPUT_TF_HLG = 3 << INPUT_TF_SHIFT,
-
-            OUTPUT_TF_SHIFT = 10,
-            OUTPUT_TF_MASK = 3 << OUTPUT_TF_SHIFT,
-            OUTPUT_TF_LINEAR = 0 << OUTPUT_TF_SHIFT,
-            OUTPUT_TF_SRGB = 1 << OUTPUT_TF_SHIFT,
-            OUTPUT_TF_ST2084 = 2 << OUTPUT_TF_SHIFT,
-            OUTPUT_TF_HLG = 3 << OUTPUT_TF_SHIFT,
-
-            Y410_BT2020_SHIFT = 12,
-            Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT,
-            Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT,
-            Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT,
-
-            SHADOW_SHIFT = 13,
-            SHADOW_MASK = 1 << SHADOW_SHIFT,
-            SHADOW_OFF = 0 << SHADOW_SHIFT,
-            SHADOW_ON = 1 << SHADOW_SHIFT,
-
-            DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT = 14,
-            DISPLAY_COLOR_TRANSFORM_MATRIX_MASK = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
-            DISPLAY_COLOR_TRANSFORM_MATRIX_OFF = 0 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
-            DISPLAY_COLOR_TRANSFORM_MATRIX_ON = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
-        };
-
-        inline Key() : mKey(0) {}
-        inline Key(const Key& rhs) : mKey(rhs.mKey) {}
-
-        inline Key& set(key_t mask, key_t value) {
-            mKey = (mKey & ~mask) | value;
-            return *this;
-        }
-
-        inline bool isTexturing() const { return (mKey & TEXTURE_MASK) != TEXTURE_OFF; }
-        inline bool hasTextureCoords() const { return isTexturing() && !drawShadows(); }
-        inline int getTextureTarget() const { return (mKey & TEXTURE_MASK); }
-        inline bool isPremultiplied() const { return (mKey & BLEND_MASK) == BLEND_PREMULT; }
-        inline bool isOpaque() const { return (mKey & OPACITY_MASK) == OPACITY_OPAQUE; }
-        inline bool hasAlpha() const { return (mKey & ALPHA_MASK) == ALPHA_LT_ONE; }
-        inline bool hasRoundedCorners() const {
-            return (mKey & ROUNDED_CORNERS_MASK) == ROUNDED_CORNERS_ON;
-        }
-        inline bool drawShadows() const { return (mKey & SHADOW_MASK) == SHADOW_ON; }
-        inline bool hasInputTransformMatrix() const {
-            return (mKey & INPUT_TRANSFORM_MATRIX_MASK) == INPUT_TRANSFORM_MATRIX_ON;
-        }
-        inline bool hasOutputTransformMatrix() const {
-            return (mKey & OUTPUT_TRANSFORM_MATRIX_MASK) == OUTPUT_TRANSFORM_MATRIX_ON;
-        }
-        inline bool hasDisplayColorMatrix() const {
-            return (mKey & DISPLAY_COLOR_TRANSFORM_MATRIX_MASK) ==
-                    DISPLAY_COLOR_TRANSFORM_MATRIX_ON;
-        }
-        inline bool hasTransformMatrix() const {
-            return hasInputTransformMatrix() || hasOutputTransformMatrix();
-        }
-        inline int getInputTF() const { return (mKey & INPUT_TF_MASK); }
-        inline int getOutputTF() const { return (mKey & OUTPUT_TF_MASK); }
-
-        // When HDR and non-HDR contents are mixed, or different types of HDR contents are
-        // mixed, we will do a tone mapping process to tone map the input content to output
-        // content. Currently, the following conversions handled, they are:
-        // * SDR -> HLG
-        // * SDR -> PQ
-        // * HLG -> PQ
-        inline bool needsToneMapping() const {
-            int inputTF = getInputTF();
-            int outputTF = getOutputTF();
-
-            // Return false when converting from SDR to SDR.
-            if (inputTF == Key::INPUT_TF_SRGB && outputTF == Key::OUTPUT_TF_LINEAR) {
-                return false;
-            }
-            if (inputTF == Key::INPUT_TF_LINEAR && outputTF == Key::OUTPUT_TF_SRGB) {
-                return false;
-            }
-
-            inputTF >>= Key::INPUT_TF_SHIFT;
-            outputTF >>= Key::OUTPUT_TF_SHIFT;
-            return inputTF != outputTF;
-        }
-        inline bool isY410BT2020() const { return (mKey & Y410_BT2020_MASK) == Y410_BT2020_ON; }
-
-        // for use by std::unordered_map
-
-        bool operator==(const Key& other) const { return mKey == other.mKey; }
-
-        struct Hash {
-            size_t operator()(const Key& key) const { return static_cast<size_t>(key.mKey); }
-        };
-    };
-
-    ProgramCache() = default;
-    ~ProgramCache() = default;
-
-    // Generate shaders to populate the cache
-    void primeCache(const EGLContext context, bool useColorManagement, bool toneMapperShaderOnly);
-
-    size_t getSize(const EGLContext context) { return mCaches[context].size(); }
-
-    // useProgram lookup a suitable program in the cache or generates one
-    // if none can be found.
-    void useProgram(const EGLContext context, const Description& description);
-
-    void purgeCaches() { mCaches.clear(); }
-
-private:
-    // compute a cache Key from a Description
-    static Key computeKey(const Description& description);
-    // Generate EOTF based from Key.
-    static void generateEOTF(Formatter& fs, const Key& needs);
-    // Generate necessary tone mapping methods for OOTF.
-    static void generateToneMappingProcess(Formatter& fs, const Key& needs);
-    // Generate OOTF based from Key.
-    static void generateOOTF(Formatter& fs, const Key& needs);
-    // Generate OETF based from Key.
-    static void generateOETF(Formatter& fs, const Key& needs);
-    // generates a program from the Key
-    static std::unique_ptr<Program> generateProgram(const Key& needs);
-    // generates the vertex shader from the Key
-    static String8 generateVertexShader(const Key& needs);
-    // generates the fragment shader from the Key
-    static String8 generateFragmentShader(const Key& needs);
-
-    // Key/Value map used for caching Programs. Currently the cache
-    // is never shrunk (and the GL program objects are never deleted).
-    std::unordered_map<EGLContext, std::unordered_map<Key, std::unique_ptr<Program>, Key::Hash>>
-            mCaches;
-};
-
-} // namespace gl
-} // namespace renderengine
-
-ANDROID_BASIC_TYPES_TRAITS(renderengine::gl::ProgramCache::Key)
-
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_PROGRAMCACHE_H */
diff --git a/libs/renderengine/gl/filters/BlurFilter.cpp b/libs/renderengine/gl/filters/BlurFilter.cpp
deleted file mode 100644
index 3455e08..0000000
--- a/libs/renderengine/gl/filters/BlurFilter.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "BlurFilter.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#include <ui/GraphicTypes.h>
-#include <cstdint>
-
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-BlurFilter::BlurFilter(GLESRenderEngine& engine)
-      : mEngine(engine),
-        mCompositionFbo(engine),
-        mPingFbo(engine),
-        mPongFbo(engine),
-        mMixProgram(engine),
-        mBlurProgram(engine) {
-    mMixProgram.compile(getVertexShader(), getMixFragShader());
-    mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
-    mMUvLoc = mMixProgram.getAttributeLocation("aUV");
-    mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
-    mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
-    mMMixLoc = mMixProgram.getUniformLocation("uMix");
-
-    mBlurProgram.compile(getVertexShader(), getFragmentShader());
-    mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
-    mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
-    mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
-    mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");
-
-    static constexpr auto size = 2.0f;
-    static constexpr auto translation = 1.0f;
-    const GLfloat vboData[] = {
-        // Vertex data
-        translation - size, -translation - size,
-        translation - size, -translation + size,
-        translation + size, -translation + size,
-        // UV data
-        0.0f, 0.0f - translation,
-        0.0f, size - translation,
-        size, size - translation
-    };
-    mMeshBuffer.allocateBuffers(vboData, 12 /* size */);
-}
-
-status_t BlurFilter::setAsDrawTarget(const DisplaySettings& display, uint32_t radius) {
-    ATRACE_NAME("BlurFilter::setAsDrawTarget");
-    mRadius = radius;
-    mDisplayX = display.physicalDisplay.left;
-    mDisplayY = display.physicalDisplay.top;
-
-    if (mDisplayWidth < display.physicalDisplay.width() ||
-        mDisplayHeight < display.physicalDisplay.height()) {
-        ATRACE_NAME("BlurFilter::allocatingTextures");
-
-        mDisplayWidth = display.physicalDisplay.width();
-        mDisplayHeight = display.physicalDisplay.height();
-        mCompositionFbo.allocateBuffers(mDisplayWidth, mDisplayHeight);
-
-        const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
-        const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
-        mPingFbo.allocateBuffers(fboWidth, fboHeight);
-        mPongFbo.allocateBuffers(fboWidth, fboHeight);
-
-        if (mPingFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
-            ALOGE("Invalid ping buffer");
-            return mPingFbo.getStatus();
-        }
-        if (mPongFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
-            ALOGE("Invalid pong buffer");
-            return mPongFbo.getStatus();
-        }
-        if (mCompositionFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
-            ALOGE("Invalid composition buffer");
-            return mCompositionFbo.getStatus();
-        }
-        if (!mBlurProgram.isValid()) {
-            ALOGE("Invalid shader");
-            return GL_INVALID_OPERATION;
-        }
-    }
-
-    mCompositionFbo.bind();
-    glViewport(0, 0, mCompositionFbo.getBufferWidth(), mCompositionFbo.getBufferHeight());
-    return NO_ERROR;
-}
-
-void BlurFilter::drawMesh(GLuint uv, GLuint position) {
-
-    glEnableVertexAttribArray(uv);
-    glEnableVertexAttribArray(position);
-    mMeshBuffer.bind();
-    glVertexAttribPointer(position, 2 /* size */, GL_FLOAT, GL_FALSE,
-                          2 * sizeof(GLfloat) /* stride */, 0 /* offset */);
-    glVertexAttribPointer(uv, 2 /* size */, GL_FLOAT, GL_FALSE, 0 /* stride */,
-                          (GLvoid*)(6 * sizeof(GLfloat)) /* offset */);
-    mMeshBuffer.unbind();
-
-    // draw mesh
-    glDrawArrays(GL_TRIANGLES, 0 /* first */, 3 /* count */);
-}
-
-status_t BlurFilter::prepare() {
-    ATRACE_NAME("BlurFilter::prepare");
-
-    // Kawase is an approximation of Gaussian, but it behaves differently from it.
-    // A radius transformation is required for approximating them, and also to introduce
-    // non-integer steps, necessary to smoothly interpolate large radii.
-    const auto radius = mRadius / 6.0f;
-
-    // Calculate how many passes we'll do, based on the radius.
-    // Too many passes will make the operation expensive.
-    const auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
-
-    const float radiusByPasses = radius / (float)passes;
-    const float stepX = radiusByPasses / (float)mCompositionFbo.getBufferWidth();
-    const float stepY = radiusByPasses / (float)mCompositionFbo.getBufferHeight();
-
-    // Let's start by downsampling and blurring the composited frame simultaneously.
-    mBlurProgram.useProgram();
-    glActiveTexture(GL_TEXTURE0);
-    glUniform1i(mBTextureLoc, 0);
-    glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
-    glUniform2f(mBOffsetLoc, stepX, stepY);
-    glViewport(0, 0, mPingFbo.getBufferWidth(), mPingFbo.getBufferHeight());
-    mPingFbo.bind();
-    drawMesh(mBUvLoc, mBPosLoc);
-
-    // And now we'll ping pong between our textures, to accumulate the result of various offsets.
-    GLFramebuffer* read = &mPingFbo;
-    GLFramebuffer* draw = &mPongFbo;
-    glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
-    for (auto i = 1; i < passes; i++) {
-        ATRACE_NAME("BlurFilter::renderPass");
-        draw->bind();
-
-        glBindTexture(GL_TEXTURE_2D, read->getTextureName());
-        glUniform2f(mBOffsetLoc, stepX * i, stepY * i);
-
-        drawMesh(mBUvLoc, mBPosLoc);
-
-        // Swap buffers for next iteration
-        auto tmp = draw;
-        draw = read;
-        read = tmp;
-    }
-    mLastDrawTarget = read;
-
-    return NO_ERROR;
-}
-
-status_t BlurFilter::render(bool multiPass) {
-    ATRACE_NAME("BlurFilter::render");
-
-    // Now let's scale our blur up. It will be interpolated with the larger composited
-    // texture for the first frames, to hide downscaling artifacts.
-    GLfloat mix = fmin(1.0, mRadius / kMaxCrossFadeRadius);
-
-    // When doing multiple passes, we cannot try to read mCompositionFbo, given that we'll
-    // be writing onto it. Let's disable the crossfade, otherwise we'd need 1 extra frame buffer,
-    // as large as the screen size.
-    if (mix >= 1 || multiPass) {
-        mLastDrawTarget->bindAsReadBuffer();
-        glBlitFramebuffer(0, 0, mLastDrawTarget->getBufferWidth(),
-                          mLastDrawTarget->getBufferHeight(), mDisplayX, mDisplayY, mDisplayWidth,
-                          mDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
-        return NO_ERROR;
-    }
-
-    mMixProgram.useProgram();
-    glUniform1f(mMMixLoc, mix);
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, mLastDrawTarget->getTextureName());
-    glUniform1i(mMTextureLoc, 0);
-    glActiveTexture(GL_TEXTURE1);
-    glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
-    glUniform1i(mMCompositionTextureLoc, 1);
-
-    drawMesh(mMUvLoc, mMPosLoc);
-
-    glUseProgram(0);
-    glActiveTexture(GL_TEXTURE0);
-    mEngine.checkErrors("Drawing blur mesh");
-    return NO_ERROR;
-}
-
-string BlurFilter::getVertexShader() const {
-    return R"SHADER(#version 300 es
-        precision mediump float;
-
-        in vec2 aPosition;
-        in highp vec2 aUV;
-        out highp vec2 vUV;
-
-        void main() {
-            vUV = aUV;
-            gl_Position = vec4(aPosition, 0.0, 1.0);
-        }
-    )SHADER";
-}
-
-string BlurFilter::getFragmentShader() const {
-    return R"SHADER(#version 300 es
-        precision mediump float;
-
-        uniform sampler2D uTexture;
-        uniform vec2 uOffset;
-
-        in highp vec2 vUV;
-        out vec4 fragColor;
-
-        void main() {
-            fragColor  = texture(uTexture, vUV, 0.0);
-            fragColor += texture(uTexture, vUV + vec2( uOffset.x,  uOffset.y), 0.0);
-            fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
-            fragColor += texture(uTexture, vUV + vec2(-uOffset.x,  uOffset.y), 0.0);
-            fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
-
-            fragColor = vec4(fragColor.rgb * 0.2, 1.0);
-        }
-    )SHADER";
-}
-
-string BlurFilter::getMixFragShader() const {
-    string shader = R"SHADER(#version 300 es
-        precision mediump float;
-
-        in highp vec2 vUV;
-        out vec4 fragColor;
-
-        uniform sampler2D uCompositionTexture;
-        uniform sampler2D uTexture;
-        uniform float uMix;
-
-        void main() {
-            vec4 blurred = texture(uTexture, vUV);
-            vec4 composition = texture(uCompositionTexture, vUV);
-            fragColor = mix(composition, blurred, uMix);
-        }
-    )SHADER";
-    return shader;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/BlurFilter.h b/libs/renderengine/gl/filters/BlurFilter.h
deleted file mode 100644
index 593a8fd..0000000
--- a/libs/renderengine/gl/filters/BlurFilter.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-#include "../GLVertexBuffer.h"
-#include "GenericProgram.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * This is an implementation of a Kawase blur, as described in here:
- * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/
- * 00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
- */
-class BlurFilter {
-public:
-    // Downsample FBO to improve performance
-    static constexpr float kFboScale = 0.25f;
-    // Maximum number of render passes
-    static constexpr uint32_t kMaxPasses = 4;
-    // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
-    // image, up to this radius.
-    static constexpr float kMaxCrossFadeRadius = 30.0f;
-
-    explicit BlurFilter(GLESRenderEngine& engine);
-    virtual ~BlurFilter(){};
-
-    // Set up render targets, redirecting output to offscreen texture.
-    status_t setAsDrawTarget(const DisplaySettings&, uint32_t radius);
-    // Execute blur passes, rendering to offscreen texture.
-    status_t prepare();
-    // Render blur to the bound framebuffer (screen).
-    status_t render(bool multiPass);
-
-private:
-    uint32_t mRadius;
-    void drawMesh(GLuint uv, GLuint position);
-    string getVertexShader() const;
-    string getFragmentShader() const;
-    string getMixFragShader() const;
-
-    GLESRenderEngine& mEngine;
-    // Frame buffer holding the composited background.
-    GLFramebuffer mCompositionFbo;
-    // Frame buffers holding the blur passes.
-    GLFramebuffer mPingFbo;
-    GLFramebuffer mPongFbo;
-    uint32_t mDisplayWidth = 0;
-    uint32_t mDisplayHeight = 0;
-    uint32_t mDisplayX = 0;
-    uint32_t mDisplayY = 0;
-    // Buffer holding the final blur pass.
-    GLFramebuffer* mLastDrawTarget;
-
-    // VBO containing vertex and uv data of a fullscreen triangle.
-    GLVertexBuffer mMeshBuffer;
-
-    GenericProgram mMixProgram;
-    GLuint mMPosLoc;
-    GLuint mMUvLoc;
-    GLuint mMMixLoc;
-    GLuint mMTextureLoc;
-    GLuint mMCompositionTextureLoc;
-
-    GenericProgram mBlurProgram;
-    GLuint mBPosLoc;
-    GLuint mBUvLoc;
-    GLuint mBTextureLoc;
-    GLuint mBOffsetLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GenericProgram.cpp b/libs/renderengine/gl/filters/GenericProgram.cpp
deleted file mode 100644
index bb35889..0000000
--- a/libs/renderengine/gl/filters/GenericProgram.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "GenericProgram.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GenericProgram::GenericProgram(GLESRenderEngine& engine) : mEngine(engine) {}
-
-GenericProgram::~GenericProgram() {
-    if (mVertexShaderHandle != 0) {
-        if (mProgramHandle != 0) {
-            glDetachShader(mProgramHandle, mVertexShaderHandle);
-        }
-        glDeleteShader(mVertexShaderHandle);
-    }
-
-    if (mFragmentShaderHandle != 0) {
-        if (mProgramHandle != 0) {
-            glDetachShader(mProgramHandle, mFragmentShaderHandle);
-        }
-        glDeleteShader(mFragmentShaderHandle);
-    }
-
-    if (mProgramHandle != 0) {
-        glDeleteProgram(mProgramHandle);
-    }
-}
-
-void GenericProgram::compile(string vertexShader, string fragmentShader) {
-    mVertexShaderHandle = compileShader(GL_VERTEX_SHADER, vertexShader);
-    mFragmentShaderHandle = compileShader(GL_FRAGMENT_SHADER, fragmentShader);
-    if (mVertexShaderHandle == 0 || mFragmentShaderHandle == 0) {
-        ALOGE("Aborting program creation.");
-        return;
-    }
-    mProgramHandle = createAndLink(mVertexShaderHandle, mFragmentShaderHandle);
-    mEngine.checkErrors("Linking program");
-}
-
-void GenericProgram::useProgram() const {
-    glUseProgram(mProgramHandle);
-}
-
-GLuint GenericProgram::compileShader(GLuint type, string src) const {
-    const GLuint shader = glCreateShader(type);
-    if (shader == 0) {
-        mEngine.checkErrors("Creating shader");
-        return 0;
-    }
-    const GLchar* charSrc = (const GLchar*)src.c_str();
-    glShaderSource(shader, 1, &charSrc, nullptr);
-    glCompileShader(shader);
-
-    GLint isCompiled = 0;
-    glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
-    if (isCompiled == GL_FALSE) {
-        GLint maxLength = 0;
-        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
-        string errorLog;
-        errorLog.reserve(maxLength);
-        glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data());
-        glDeleteShader(shader);
-        ALOGE("Error compiling shader: %s", errorLog.c_str());
-        return 0;
-    }
-    return shader;
-}
-GLuint GenericProgram::createAndLink(GLuint vertexShader, GLuint fragmentShader) const {
-    const GLuint program = glCreateProgram();
-    mEngine.checkErrors("Creating program");
-
-    glAttachShader(program, vertexShader);
-    glAttachShader(program, fragmentShader);
-    glLinkProgram(program);
-    mEngine.checkErrors("Linking program");
-    return program;
-}
-
-GLuint GenericProgram::getUniformLocation(const string name) const {
-    if (mProgramHandle == 0) {
-        ALOGE("Can't get location of %s on an invalid program.", name.c_str());
-        return -1;
-    }
-    return glGetUniformLocation(mProgramHandle, (const GLchar*)name.c_str());
-}
-
-GLuint GenericProgram::getAttributeLocation(const string name) const {
-    if (mProgramHandle == 0) {
-        ALOGE("Can't get location of %s on an invalid program.", name.c_str());
-        return -1;
-    }
-    return glGetAttribLocation(mProgramHandle, (const GLchar*)name.c_str());
-}
-
-bool GenericProgram::isValid() const {
-    return mProgramHandle != 0;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GenericProgram.h b/libs/renderengine/gl/filters/GenericProgram.h
deleted file mode 100644
index 6da2a5a..0000000
--- a/libs/renderengine/gl/filters/GenericProgram.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GenericProgram {
-public:
-    explicit GenericProgram(GLESRenderEngine& renderEngine);
-    ~GenericProgram();
-    void compile(string vertexShader, string fragmentShader);
-    bool isValid() const;
-    void useProgram() const;
-    GLuint getAttributeLocation(const string name) const;
-    GLuint getUniformLocation(const string name) const;
-
-private:
-    GLuint compileShader(GLuint type, const string src) const;
-    GLuint createAndLink(GLuint vertexShader, GLuint fragmentShader) const;
-
-    GLESRenderEngine& mEngine;
-    GLuint mVertexShaderHandle = 0;
-    GLuint mFragmentShaderHandle = 0;
-    GLuint mProgramHandle = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/Framebuffer.h b/libs/renderengine/include/renderengine/Framebuffer.h
deleted file mode 100644
index 6511127..0000000
--- a/libs/renderengine/include/renderengine/Framebuffer.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cstdint>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-
-class Framebuffer {
-public:
-    virtual ~Framebuffer() = default;
-
-    virtual bool setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
-                                       const bool useFramebufferCache) = 0;
-};
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index b3a617c..8ac0af4 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -28,6 +28,7 @@
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
+#include <ui/ShadowSettings.h>
 #include <ui/StretchEffect.h>
 #include <ui/Transform.h>
 
@@ -46,10 +47,6 @@
     // Fence that will fire when the buffer is ready to be bound.
     sp<Fence> fence = nullptr;
 
-    // Texture identifier to bind the external texture to.
-    // TODO(alecmouri): This is GL-specific...make the type backend-agnostic.
-    uint32_t textureName = 0;
-
     // Whether to use filtering when rendering the texture.
     bool useTextureFiltering = false;
 
@@ -64,9 +61,6 @@
     // overrides the alpha channel of the buffer.
     bool isOpaque = false;
 
-    // HDR color-space setting for Y410.
-    bool isY410BT2020 = false;
-
     float maxLuminanceNits = 0.0;
 };
 
@@ -104,36 +98,6 @@
     half3 solidColor = half3(0.0f, 0.0f, 0.0f);
 };
 
-/*
- * Contains the configuration for the shadows drawn by single layer. Shadow follows
- * material design guidelines.
- */
-struct ShadowSettings {
-    // Boundaries of the shadow.
-    FloatRect boundaries = FloatRect();
-
-    // Color to the ambient shadow. The alpha is premultiplied.
-    vec4 ambientColor = vec4();
-
-    // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
-    // depends on the light position.
-    vec4 spotColor = vec4();
-
-    // Position of the light source used to cast the spot shadow.
-    vec3 lightPos = vec3();
-
-    // Radius of the spot light source. Smaller radius will have sharper edges,
-    // larger radius will have softer shadows
-    float lightRadius = 0.f;
-
-    // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
-    float length = 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;
-};
-
 // The settings that RenderEngine requires for correctly rendering a Layer.
 struct LayerSettings {
     // Geometry information
@@ -185,12 +149,10 @@
 // compositionengine/impl/ClientCompositionRequestCache.cpp
 static inline bool operator==(const Buffer& lhs, const Buffer& rhs) {
     return lhs.buffer == rhs.buffer && lhs.fence == rhs.fence &&
-            lhs.textureName == rhs.textureName &&
             lhs.useTextureFiltering == rhs.useTextureFiltering &&
             lhs.textureTransform == rhs.textureTransform &&
             lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
-            lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
-            lhs.maxLuminanceNits == rhs.maxLuminanceNits;
+            lhs.isOpaque == rhs.isOpaque && lhs.maxLuminanceNits == rhs.maxLuminanceNits;
 }
 
 static inline bool operator==(const Geometry& lhs, const Geometry& rhs) {
@@ -203,17 +165,6 @@
     return lhs.buffer == rhs.buffer && lhs.solidColor == rhs.solidColor;
 }
 
-static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
-    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.casterIsTranslucent == rhs.casterIsTranslucent;
-}
-
-static inline bool operator!=(const ShadowSettings& lhs, const ShadowSettings& rhs) {
-    return !(operator==(lhs, rhs));
-}
-
 static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
     if (lhs.blurRegions.size() != rhs.blurRegions.size()) {
         return false;
@@ -241,13 +192,11 @@
         << (settings.buffer.get() ? decodePixelFormat(settings.buffer->getPixelFormat()).c_str()
                                   : "");
     *os << "\n    .fence = " << settings.fence.get();
-    *os << "\n    .textureName = " << settings.textureName;
     *os << "\n    .useTextureFiltering = " << settings.useTextureFiltering;
     *os << "\n    .textureTransform = ";
     PrintMatrix(settings.textureTransform, os);
     *os << "\n    .usePremultipliedAlpha = " << settings.usePremultipliedAlpha;
     *os << "\n    .isOpaque = " << settings.isOpaque;
-    *os << "\n    .isY410BT2020 = " << settings.isY410BT2020;
     *os << "\n    .maxLuminanceNits = " << settings.maxLuminanceNits;
     *os << "\n}";
 }
diff --git a/libs/renderengine/include/renderengine/Mesh.h b/libs/renderengine/include/renderengine/Mesh.h
deleted file mode 100644
index 167f13f..0000000
--- a/libs/renderengine/include/renderengine/Mesh.h
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 SF_RENDER_ENGINE_MESH_H
-#define SF_RENDER_ENGINE_MESH_H
-
-#include <vector>
-
-#include <stdint.h>
-
-namespace android {
-namespace renderengine {
-
-class Mesh {
-public:
-    class Builder;
-
-    enum Primitive {
-        TRIANGLES = 0x0004,      // GL_TRIANGLES
-        TRIANGLE_STRIP = 0x0005, // GL_TRIANGLE_STRIP
-        TRIANGLE_FAN = 0x0006    // GL_TRIANGLE_FAN
-    };
-
-    ~Mesh() = default;
-
-    /*
-     * VertexArray handles the stride automatically.
-     */
-    template <typename TYPE>
-    class VertexArray {
-        friend class Mesh;
-        float* mData;
-        size_t mStride;
-        size_t mOffset = 0;
-        VertexArray(float* data, size_t stride) : mData(data), mStride(stride) {}
-
-    public:
-        // Returns a vertex array at an offset so its easier to append attributes from
-        // multiple sources.
-        VertexArray(VertexArray<TYPE>& other, size_t offset)
-              : mData(other.mData), mStride(other.mStride), mOffset(offset) {}
-
-        TYPE& operator[](size_t index) {
-            return *reinterpret_cast<TYPE*>(&mData[(index + mOffset) * mStride]);
-        }
-        TYPE const& operator[](size_t index) const {
-            return *reinterpret_cast<TYPE const*>(&mData[(index + mOffset) * mStride]);
-        }
-    };
-
-    template <typename TYPE>
-    VertexArray<TYPE> getPositionArray() {
-        return VertexArray<TYPE>(getPositions(), mStride);
-    }
-
-    template <typename TYPE>
-    VertexArray<TYPE> getTexCoordArray() {
-        return VertexArray<TYPE>(getTexCoords(), mStride);
-    }
-
-    template <typename TYPE>
-    VertexArray<TYPE> getCropCoordArray() {
-        return VertexArray<TYPE>(getCropCoords(), mStride);
-    }
-
-    template <typename TYPE>
-    VertexArray<TYPE> getShadowColorArray() {
-        return VertexArray<TYPE>(getShadowColor(), mStride);
-    }
-
-    template <typename TYPE>
-    VertexArray<TYPE> getShadowParamsArray() {
-        return VertexArray<TYPE>(getShadowParams(), mStride);
-    }
-
-    uint16_t* getIndicesArray() { return getIndices(); }
-
-    Primitive getPrimitive() const;
-
-    // returns a pointer to the vertices positions
-    float const* getPositions() const;
-
-    // returns a pointer to the vertices texture coordinates
-    float const* getTexCoords() const;
-
-    // returns a pointer to the vertices crop coordinates
-    float const* getCropCoords() const;
-
-    // returns a pointer to colors
-    float const* getShadowColor() const;
-
-    // returns a pointer to the shadow params
-    float const* getShadowParams() const;
-
-    // returns a pointer to indices
-    uint16_t const* getIndices() const;
-
-    // number of vertices in this mesh
-    size_t getVertexCount() const;
-
-    // dimension of vertices
-    size_t getVertexSize() const;
-
-    // dimension of texture coordinates
-    size_t getTexCoordsSize() const;
-
-    size_t getShadowParamsSize() const;
-
-    size_t getShadowColorSize() const;
-
-    size_t getIndexCount() const;
-
-    // return stride in bytes
-    size_t getByteStride() const;
-
-    // return stride in floats
-    size_t getStride() const;
-
-private:
-    Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
-         size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize, size_t indexCount);
-    Mesh(const Mesh&);
-    Mesh& operator=(const Mesh&);
-    Mesh const& operator=(const Mesh&) const;
-
-    float* getPositions();
-    float* getTexCoords();
-    float* getCropCoords();
-    float* getShadowColor();
-    float* getShadowParams();
-    uint16_t* getIndices();
-
-    std::vector<float> mVertices;
-    size_t mVertexCount;
-    size_t mVertexSize;
-    size_t mTexCoordsSize;
-    size_t mCropCoordsSize;
-    size_t mShadowColorSize;
-    size_t mShadowParamsSize;
-    size_t mStride;
-    Primitive mPrimitive;
-    std::vector<uint16_t> mIndices;
-    size_t mIndexCount;
-};
-
-class Mesh::Builder {
-public:
-    Builder& setPrimitive(Primitive primitive) {
-        mPrimitive = primitive;
-        return *this;
-    };
-    Builder& setVertices(size_t vertexCount, size_t vertexSize) {
-        mVertexCount = vertexCount;
-        mVertexSize = vertexSize;
-        return *this;
-    };
-    Builder& setTexCoords(size_t texCoordsSize) {
-        mTexCoordsSize = texCoordsSize;
-        return *this;
-    };
-    Builder& setCropCoords(size_t cropCoordsSize) {
-        mCropCoordsSize = cropCoordsSize;
-        return *this;
-    };
-    Builder& setShadowAttrs() {
-        mShadowParamsSize = 3;
-        mShadowColorSize = 4;
-        return *this;
-    };
-    Builder& setIndices(size_t indexCount) {
-        mIndexCount = indexCount;
-        return *this;
-    };
-    Mesh build() const {
-        return Mesh{mPrimitive,      mVertexCount,     mVertexSize,       mTexCoordsSize,
-                    mCropCoordsSize, mShadowColorSize, mShadowParamsSize, mIndexCount};
-    }
-
-private:
-    size_t mVertexCount = 0;
-    size_t mVertexSize = 0;
-    size_t mTexCoordsSize = 0;
-    size_t mCropCoordsSize = 0;
-    size_t mShadowColorSize = 0;
-    size_t mShadowParamsSize = 0;
-    size_t mIndexCount = 0;
-    Primitive mPrimitive;
-};
-
-} // namespace renderengine
-} // namespace android
-#endif /* SF_RENDER_ENGINE_MESH_H */
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 0d910c9..818d035 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -22,8 +22,6 @@
 #include <math/mat4.h>
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/ExternalTexture.h>
-#include <renderengine/Framebuffer.h>
-#include <renderengine/Image.h>
 #include <renderengine/LayerSettings.h>
 #include <stdint.h>
 #include <sys/types.h>
@@ -95,8 +93,6 @@
     };
 
     enum class RenderEngineType {
-        GLES = 1,
-        THREADED = 2,
         SKIA_GL = 3,
         SKIA_GL_THREADED = 4,
         SKIA_VK = 5,
@@ -111,14 +107,11 @@
     // This interface, while still in use until a suitable replacement is built,
     // should be considered deprecated, minus some methods which still may be
     // used to support legacy behavior.
-    virtual std::future<void> primeCache() = 0;
+    virtual std::future<void> primeCache(bool shouldPrimeUltraHDR) = 0;
 
     // dump the extension strings. always call the base class.
     virtual void dump(std::string& result) = 0;
 
-    virtual void genTextures(size_t count, uint32_t* names) = 0;
-    virtual void deleteTextures(size_t count, uint32_t const* names) = 0;
-
     // queries that are required to be thread safe
     virtual size_t getMaxTextureSize() const = 0;
     virtual size_t getMaxViewportDims() const = 0;
@@ -152,9 +145,6 @@
     // @param layers The layers to draw onto the display, in Z-order.
     // @param buffer The buffer which will be drawn to. This buffer will be
     // ready once drawFence fires.
-    // @param useFramebufferCache True if the framebuffer cache should be used.
-    // If an implementation does not cache output framebuffers, then this
-    // parameter does nothing.
     // @param bufferFence Fence signalling that the buffer is ready to be drawn
     // to.
     // @return A future object of FenceResult indicating whether drawing was
@@ -162,7 +152,6 @@
     virtual ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
                                                 const std::vector<LayerSettings>& layers,
                                                 const std::shared_ptr<ExternalTexture>& buffer,
-                                                const bool useFramebufferCache,
                                                 base::unique_fd&& bufferFence);
 
     // Clean-up method that should be called on the main thread after the
@@ -171,8 +160,6 @@
     // being drawn, then the implementation is free to silently ignore this call.
     virtual void cleanupPostRender() = 0;
 
-    virtual void cleanFramebufferCache() = 0;
-
     // Returns the priority this context was actually created with. Note: this
     // may not be the same as specified at context creation time, due to
     // implementation limits on the number of contexts that can be created at a
@@ -204,7 +191,7 @@
     virtual void setEnableTracing(bool /*tracingEnabled*/) {}
 
 protected:
-    RenderEngine() : RenderEngine(RenderEngineType::GLES) {}
+    RenderEngine() : RenderEngine(RenderEngineType::SKIA_GL) {}
 
     RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
 
@@ -253,8 +240,7 @@
     virtual void drawLayersInternal(
             const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
             const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-            const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
-            base::unique_fd&& bufferFence) = 0;
+            const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
 };
 
 struct RenderEngineCreationArgs {
@@ -271,14 +257,13 @@
 
 private:
     // must be created by Builder via constructor with full argument list
-    RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize, bool _useColorManagement,
+    RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize,
                              bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
                              bool _supportsBackgroundBlur,
                              RenderEngine::ContextPriority _contextPriority,
                              RenderEngine::RenderEngineType _renderEngineType)
           : pixelFormat(_pixelFormat),
             imageCacheSize(_imageCacheSize),
-            useColorManagement(_useColorManagement),
             enableProtectedContext(_enableProtectedContext),
             precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
             supportsBackgroundBlur(_supportsBackgroundBlur),
@@ -298,10 +283,6 @@
         this->imageCacheSize = imageCacheSize;
         return *this;
     }
-    Builder& setUseColorManagerment(bool useColorManagement) {
-        this->useColorManagement = useColorManagement;
-        return *this;
-    }
     Builder& setEnableProtectedContext(bool enableProtectedContext) {
         this->enableProtectedContext = enableProtectedContext;
         return *this;
@@ -323,16 +304,15 @@
         return *this;
     }
     RenderEngineCreationArgs build() const {
-        return RenderEngineCreationArgs(pixelFormat, imageCacheSize, useColorManagement,
-                                        enableProtectedContext, precacheToneMapperShaderOnly,
-                                        supportsBackgroundBlur, contextPriority, renderEngineType);
+        return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
+                                        precacheToneMapperShaderOnly, supportsBackgroundBlur,
+                                        contextPriority, renderEngineType);
     }
 
 private:
     // 1 means RGBA_8888
     int pixelFormat = 1;
     uint32_t imageCacheSize = 0;
-    bool useColorManagement = true;
     bool enableProtectedContext = false;
     bool precacheToneMapperShaderOnly = false;
     bool supportsBackgroundBlur = false;
diff --git a/libs/renderengine/include/renderengine/Texture.h b/libs/renderengine/include/renderengine/Texture.h
deleted file mode 100644
index c69ace0..0000000
--- a/libs/renderengine/include/renderengine/Texture.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 SF_RENDER_ENGINE_TEXTURE_H
-#define SF_RENDER_ENGINE_TEXTURE_H
-
-#include <stdint.h>
-
-#include <math/mat4.h>
-
-namespace android {
-namespace renderengine {
-
-class Texture {
-public:
-    enum Target { TEXTURE_2D = 0x0DE1, TEXTURE_EXTERNAL = 0x8D65 };
-
-    Texture();
-    Texture(Target textureTarget, uint32_t textureName);
-    ~Texture();
-
-    void init(Target textureTarget, uint32_t textureName);
-
-    void setMatrix(float const* matrix);
-    void setFiltering(bool enabled);
-    void setDimensions(size_t width, size_t height);
-
-    uint32_t getTextureName() const;
-    uint32_t getTextureTarget() const;
-
-    const mat4& getMatrix() const;
-    bool getFiltering() const;
-    size_t getWidth() const;
-    size_t getHeight() const;
-
-private:
-    uint32_t mTextureName;
-    uint32_t mTextureTarget;
-    size_t mWidth;
-    size_t mHeight;
-    bool mFiltering;
-    mat4 mTextureMatrix;
-};
-
-} // namespace renderengine
-} // namespace android
-#endif /* SF_RENDER_ENGINE_TEXTURE_H */
diff --git a/libs/renderengine/include/renderengine/mock/Framebuffer.h b/libs/renderengine/include/renderengine/mock/Framebuffer.h
deleted file mode 100644
index dfb6a4e..0000000
--- a/libs/renderengine/include/renderengine/mock/Framebuffer.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-#include <renderengine/Framebuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-class Framebuffer : public renderengine::Framebuffer {
-public:
-    Framebuffer();
-    ~Framebuffer() override;
-
-    MOCK_METHOD3(setNativeWindowBuffer, bool(ANativeWindowBuffer*, bool, const bool));
-};
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/Image.h b/libs/renderengine/include/renderengine/mock/Image.h
deleted file mode 100644
index 2b0eed1..0000000
--- a/libs/renderengine/include/renderengine/mock/Image.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-#include <renderengine/Image.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-class Image : public renderengine::Image {
-public:
-    Image();
-    ~Image() override;
-
-    MOCK_METHOD2(setNativeWindowBuffer, bool(ANativeWindowBuffer* buffer, bool isProtected));
-};
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index d3035e2..a58a65c 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -19,9 +19,7 @@
 #include <gmock/gmock.h>
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/LayerSettings.h>
-#include <renderengine/Mesh.h>
 #include <renderengine/RenderEngine.h>
-#include <renderengine/Texture.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/Region.h>
@@ -35,11 +33,8 @@
     RenderEngine();
     ~RenderEngine() override;
 
-    MOCK_METHOD0(primeCache, std::future<void>());
+    MOCK_METHOD1(primeCache, std::future<void>(bool));
     MOCK_METHOD1(dump, void(std::string&));
-    MOCK_METHOD2(genTextures, void(size_t, uint32_t*));
-    MOCK_METHOD2(deleteTextures, void(size_t, uint32_t const*));
-    MOCK_METHOD1(drawMesh, void(const renderengine::Mesh&));
     MOCK_CONST_METHOD0(getMaxTextureSize, size_t());
     MOCK_CONST_METHOD0(getMaxViewportDims, size_t());
     MOCK_CONST_METHOD0(isProtected, bool());
@@ -47,15 +42,14 @@
     MOCK_METHOD1(useProtectedContext, void(bool));
     MOCK_METHOD0(cleanupPostRender, void());
     MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool());
-    MOCK_METHOD5(drawLayers,
+    MOCK_METHOD4(drawLayers,
                  ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
-                                          const std::shared_ptr<ExternalTexture>&, const bool,
+                                          const std::shared_ptr<ExternalTexture>&,
                                           base::unique_fd&&));
-    MOCK_METHOD6(drawLayersInternal,
+    MOCK_METHOD5(drawLayersInternal,
                  void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
                       const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
-                      const bool, base::unique_fd&&));
-    MOCK_METHOD0(cleanFramebufferCache, void());
+                      base::unique_fd&&));
     MOCK_METHOD0(getContextPriority, int());
     MOCK_METHOD0(supportsBackgroundBlur, bool());
     MOCK_METHOD1(onActiveDisplaySizeChanged, void(ui::Size));
diff --git a/libs/renderengine/include/renderengine/private/Description.h b/libs/renderengine/include/renderengine/private/Description.h
deleted file mode 100644
index fa6ec10..0000000
--- a/libs/renderengine/include/renderengine/private/Description.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 SF_RENDER_ENGINE_DESCRIPTION_H_
-#define SF_RENDER_ENGINE_DESCRIPTION_H_
-
-#include <renderengine/Texture.h>
-#include <ui/GraphicTypes.h>
-
-namespace android {
-namespace renderengine {
-
-/*
- * This is the structure that holds the state of the rendering engine.
- * This class is used to generate a corresponding GLSL program and set the
- * appropriate uniform.
- */
-struct Description {
-    enum class TransferFunction : int {
-        LINEAR,
-        SRGB,
-        ST2084,
-        HLG, // Hybrid Log-Gamma for HDR.
-    };
-
-    static TransferFunction dataSpaceToTransferFunction(ui::Dataspace dataSpace);
-
-    Description() = default;
-    ~Description() = default;
-
-    bool hasInputTransformMatrix() const;
-    bool hasOutputTransformMatrix() const;
-    bool hasColorMatrix() const;
-    bool hasDisplayColorMatrix() const;
-
-    // whether textures are premultiplied
-    bool isPremultipliedAlpha = false;
-    // whether this layer is marked as opaque
-    bool isOpaque = true;
-
-    // corner radius of the layer
-    float cornerRadius = 0;
-
-    // Size of the rounded rectangle we are cropping to
-    half2 cropSize;
-
-    // Texture this layer uses
-    Texture texture;
-    bool textureEnabled = false;
-
-    // color used when texturing is disabled or when setting alpha.
-    half4 color;
-
-    // true if the sampled pixel values are in Y410/BT2020 rather than RGBA
-    bool isY410BT2020 = false;
-
-    // transfer functions for the input/output
-    TransferFunction inputTransferFunction = TransferFunction::LINEAR;
-    TransferFunction outputTransferFunction = TransferFunction::LINEAR;
-
-    float displayMaxLuminance;
-    float maxMasteringLuminance;
-    float maxContentLuminance;
-
-    // projection matrix
-    mat4 projectionMatrix;
-
-    // The color matrix will be applied in linear space right before OETF.
-    mat4 colorMatrix;
-    // The display color matrix will be applied in gamma space after OETF
-    mat4 displayColorMatrix;
-    mat4 inputTransformMatrix;
-    mat4 outputTransformMatrix;
-
-    // True if this layer will draw a shadow.
-    bool drawShadows = false;
-};
-
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_DESCRIPTION_H_ */
diff --git a/libs/renderengine/mock/Framebuffer.cpp b/libs/renderengine/mock/Framebuffer.cpp
deleted file mode 100644
index fbdcaab..0000000
--- a/libs/renderengine/mock/Framebuffer.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <renderengine/mock/Framebuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Framebuffer::Framebuffer() = default;
-Framebuffer::~Framebuffer() = default;
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/mock/Image.cpp b/libs/renderengine/mock/Image.cpp
deleted file mode 100644
index 57f4346..0000000
--- a/libs/renderengine/mock/Image.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <renderengine/mock/Image.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Image::Image() = default;
-Image::~Image() = default;
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index 23c99b0..cd1ac2b 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -20,6 +20,13 @@
 #define LOG_TAG "RenderEngine"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/vk/GrVkTypes.h>
+#include <android/hardware_buffer.h>
 #include "ColorSpaces.h"
 #include "log/log_main.h"
 #include "utils/Trace.h"
@@ -35,13 +42,44 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
-    GrBackendFormat backendFormat =
-            GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
-    mBackendTexture =
-            GrAHardwareBufferUtils::MakeBackendTexture(context, buffer, desc.width, desc.height,
-                                                       &mDeleteProc, &mUpdateProc, &mImageCtx,
-                                                       createProtectedImage, backendFormat,
-                                                       isOutputBuffer);
+    GrBackendFormat backendFormat;
+
+    GrBackendApi backend = context->backend();
+    if (backend == GrBackendApi::kOpenGL) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+                                                             buffer,
+                                                             desc.width,
+                                                             desc.height,
+                                                             &mDeleteProc,
+                                                             &mUpdateProc,
+                                                             &mImageCtx,
+                                                             createProtectedImage,
+                                                             backendFormat,
+                                                             isOutputBuffer);
+    } else if (backend == GrBackendApi::kVulkan) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+                                                               buffer,
+                                                               desc.format,
+                                                               false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+                                                                 buffer,
+                                                                 desc.width,
+                                                                 desc.height,
+                                                                 &mDeleteProc,
+                                                                 &mUpdateProc,
+                                                                 &mImageCtx,
+                                                                 createProtectedImage,
+                                                                 backendFormat,
+                                                                 isOutputBuffer);
+    } else {
+        LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+    }
+
     mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
     if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
         LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
@@ -79,7 +117,7 @@
 
 // releaseImageProc is invoked by SkImage, when the texture is no longer in use.
 // "releaseContext" contains an "AutoBackendTexture*".
-void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext) {
+void AutoBackendTexture::releaseImageProc(SkImages::ReleaseContext releaseContext) {
     AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
     textureRelease->unref(false);
 }
@@ -89,7 +127,7 @@
     switch (tex.backend()) {
         case GrBackendApi::kOpenGL: {
             GrGLTextureInfo textureInfo;
-            bool retrievedTextureInfo = tex.getGLTextureInfo(&textureInfo);
+            bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &textureInfo);
             LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
                              "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
                              "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
@@ -102,7 +140,7 @@
         }
         case GrBackendApi::kVulkan: {
             GrVkImageInfo imageInfo;
-            bool retrievedImageInfo = tex.getVkImageInfo(&imageInfo);
+            bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo);
             LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
                              "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
                              "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
@@ -136,8 +174,9 @@
     }
 
     sk_sp<SkImage> image =
-            SkImage::MakeFromTexture(context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType,
-                                     alphaType, toSkColorSpace(dataspace), releaseImageProc, this);
+            SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
+                                        colorType, alphaType, toSkColorSpace(dataspace),
+                                        releaseImageProc, this);
     if (image.get()) {
         // The following ref will be counteracted by releaseProc, when SkImage is discarded.
         ref();
@@ -157,10 +196,10 @@
     LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture");
     if (!mSurface.get() || mDataspace != dataspace) {
         sk_sp<SkSurface> surface =
-                SkSurface::MakeFromBackendTexture(context, mBackendTexture,
-                                                  kTopLeft_GrSurfaceOrigin, 0, mColorType,
-                                                  toSkColorSpace(dataspace), nullptr,
-                                                  releaseSurfaceProc, this);
+                SkSurfaces::WrapBackendTexture(context, mBackendTexture,
+                                               kTopLeft_GrSurfaceOrigin, 0, mColorType,
+                                               toSkColorSpace(dataspace), nullptr,
+                                               releaseSurfaceProc, this);
         if (surface.get()) {
             // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
             ref();
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 00b901b..509ac40 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -144,7 +144,7 @@
     CleanupManager& mCleanupMgr;
 
     static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
-    static void releaseImageProc(SkImage::ReleaseContext releaseContext);
+    static void releaseImageProc(SkImages::ReleaseContext releaseContext);
 
     int mUsageCount = 0;
 
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index f6b9183..abe0d9b 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -17,6 +17,7 @@
 #include "AutoBackendTexture.h"
 #include "SkiaRenderEngine.h"
 #include "android-base/unique_fd.h"
+#include "cutils/properties.h"
 #include "renderengine/DisplaySettings.h"
 #include "renderengine/LayerSettings.h"
 #include "renderengine/impl/ExternalTexture.h"
@@ -29,8 +30,6 @@
 namespace android::renderengine::skia {
 
 namespace {
-// Warming shader cache, not framebuffer cache.
-constexpr bool kUseFrameBufferCache = false;
 
 // clang-format off
 // Any non-identity matrix will do.
@@ -51,6 +50,15 @@
 // a color correction effect is added to the shader.
 constexpr auto kDestDataSpace = ui::Dataspace::SRGB;
 constexpr auto kOtherDataSpace = ui::Dataspace::DISPLAY_P3;
+constexpr auto kBT2020DataSpace = ui::Dataspace::BT2020_ITU_PQ;
+constexpr auto kExtendedHdrDataSpce =
+        static_cast<ui::Dataspace>(ui::Dataspace::RANGE_EXTENDED | ui::Dataspace::TRANSFER_SRGB |
+                                   ui::Dataspace::STANDARD_DCI_P3);
+// Dimming is needed to trigger linear effects for some dataspace pairs
+const std::array<float, 3> kLayerWhitePoints = {
+        1000.0f, 500.0f,
+        100.0f, // trigger dithering by dimming below 20%
+};
 } // namespace
 
 static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -65,9 +73,12 @@
             .geometry =
                     Geometry{
                             .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {50.f, 50.f},
+                            .roundedCornersCrop = rect,
                     },
+            .alpha = 1,
+            // setting this is mandatory for shadows and blurs
+            .skipContentDraw = true,
             // drawShadow ignores alpha
             .shadow =
                     ShadowSettings{
@@ -78,16 +89,13 @@
                             .lightRadius = 2500.0f,
                             .length = 15.f,
                     },
-            // setting this is mandatory for shadows and blurs
-            .skipContentDraw = true,
-            .alpha = 1,
     };
     LayerSettings caster{
             .geometry =
                     Geometry{
                             .boundaries = smallerRect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {50.f, 50.f},
+                            .roundedCornersCrop = rect,
                     },
             .source =
                     PixelSource{
@@ -116,8 +124,7 @@
         caster.geometry.positionTransform = transform;
 
         auto layers = std::vector<LayerSettings>{layer, caster};
-        renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                 base::unique_fd());
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
     }
 }
 
@@ -129,10 +136,10 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
                             .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
@@ -154,8 +161,7 @@
                 for (auto alpha : {half(.2f), half(1.0f)}) {
                     layer.alpha = alpha;
                     auto layers = std::vector<LayerSettings>{layer};
-                    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                             base::unique_fd());
+                    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
                 }
             }
         }
@@ -183,8 +189,7 @@
         for (float roundedCornersRadius : {0.0f, 50.f}) {
             layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
             auto layers = std::vector<LayerSettings>{layer};
-            renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                     base::unique_fd());
+            renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
         }
     }
 }
@@ -207,8 +212,7 @@
     for (int radius : {9, 60}) {
         layer.backgroundBlurRadius = radius;
         auto layers = std::vector<LayerSettings>{layer};
-        renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                 base::unique_fd());
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
     }
 }
 
@@ -254,8 +258,7 @@
                 for (float alpha : {0.5f, 1.f}) {
                     layer.alpha = alpha;
                     auto layers = std::vector<LayerSettings>{layer};
-                    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                             base::unique_fd());
+                    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
                 }
             }
         }
@@ -270,11 +273,11 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // Note that this flip matrix only makes a difference when clipping,
                             // which happens in this layer because the roundrect crop is just a bit
                             // larger than the layer bounds.
                             .positionTransform = kFlip,
-                            .boundaries = rect,
                             .roundedCornersRadius = {94.2551f, 94.2551f},
                             .roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75,
                                                             displayRect.height()),
@@ -282,17 +285,17 @@
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
-                                                  .isOpaque = 0,
                                                   .usePremultipliedAlpha = 1,
+                                                  .isOpaque = 0,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
-            .sourceDataspace = kOtherDataSpace,
             .alpha = 1,
+            .sourceDataspace = kOtherDataSpace,
 
     };
 
     auto layers = std::vector<LayerSettings>{layer};
-    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
 }
 
 static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -303,10 +306,10 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = kScaleAndTranslate,
                             // the boundaries have to be smaller than the rounded crop so that
                             // clipRRect is used instead of drawRRect
                             .boundaries = small,
+                            .positionTransform = kScaleAndTranslate,
                             .roundedCornersRadius = {50.f, 50.f},
                             .roundedCornersCrop = rect,
                     },
@@ -314,14 +317,306 @@
                     PixelSource{
                             .solidColor = half3(0.f, 0.f, 0.f),
                     },
-            .sourceDataspace = kDestDataSpace,
             .alpha = 0,
+            .sourceDataspace = kDestDataSpace,
             .disableBlending = true,
 
     };
 
     auto layers = std::vector<LayerSettings>{layer};
-    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawImageDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                  const std::shared_ptr<ExternalTexture>& dstTexture,
+                                  const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // The position transform doesn't matter when the reduced shader mode
+                            // in in effect. A matrix transform stage is always included.
+                            .positionTransform = mat4(),
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {0.f, 0.f},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = true}},
+            .alpha = 1.f,
+            .sourceDataspace = kDestDataSpace,
+    };
+
+    std::vector<LayerSettings> layers;
+
+    for (auto layerWhitePoint : kLayerWhitePoints) {
+        layer.whitePointNits = layerWhitePoint;
+        layers.push_back(layer);
+    }
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawTransparentImageDimmedLayers(SkiaRenderEngine* renderengine,
+                                             const DisplaySettings& display,
+                                             const std::shared_ptr<ExternalTexture>& dstTexture,
+                                             const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .positionTransform = mat4(),
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                    },
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .maxLuminanceNits = 1000.f,
+                                                  .usePremultipliedAlpha = true,
+                                                  .isOpaque = false,
+                                          }},
+            .sourceDataspace = kDestDataSpace,
+    };
+
+    for (auto roundedCornerRadius : {0.f, 50.f}) {
+        layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+        for (auto alpha : {0.5f, 1.0f}) {
+            layer.alpha = alpha;
+            for (auto isOpaque : {true, false}) {
+                if (roundedCornerRadius == 0.f && isOpaque) {
+                    // already covered in drawImageDimmedLayers
+                    continue;
+                }
+
+                layer.source.buffer.isOpaque = isOpaque;
+                std::vector<LayerSettings> layers;
+
+                for (auto layerWhitePoint : kLayerWhitePoints) {
+                    layer.whitePointNits = layerWhitePoint;
+                    layers.push_back(layer);
+                }
+                renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+            }
+        }
+    }
+}
+
+static void drawClippedDimmedImageLayers(SkiaRenderEngine* renderengine,
+                                         const DisplaySettings& display,
+                                         const std::shared_ptr<ExternalTexture>& dstTexture,
+                                         const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+
+    // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+    // blending instead of EllipticalRRect, so enlarge them a bit.
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    FloatRect boundary(0, 0, displayRect.width(),
+                       displayRect.height() - 20); // boundary is smaller
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .positionTransform = mat4(),
+                            .boundaries = boundary,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {27.f, 27.f},
+                    },
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .maxLuminanceNits = 1000.f,
+                                                  .usePremultipliedAlpha = true,
+                                                  .isOpaque = false,
+                                          }},
+            .alpha = 1.f,
+            .sourceDataspace = kDestDataSpace,
+    };
+
+    std::array<mat4, 2> transforms = {kScaleAndTranslate, kScaleAsymmetric};
+
+    constexpr float radius = 27.f;
+
+    for (size_t i = 0; i < transforms.size(); i++) {
+        layer.geometry.positionTransform = transforms[i];
+        layer.geometry.roundedCornersRadius = {radius, radius};
+
+        std::vector<LayerSettings> layers;
+
+        for (auto layerWhitePoint : kLayerWhitePoints) {
+            layer.whitePointNits = layerWhitePoint;
+            layers.push_back(layer);
+        }
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+
+static void drawSolidDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                  const std::shared_ptr<ExternalTexture>& dstTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                    },
+            .source =
+                    PixelSource{
+                            .solidColor = half3(0.1f, 0.2f, 0.3f),
+                    },
+            .alpha = 1.f,
+    };
+
+    std::vector<LayerSettings> layers;
+
+    for (auto layerWhitePoint : kLayerWhitePoints) {
+        layer.whitePointNits = layerWhitePoint;
+        layers.push_back(layer);
+    }
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawBT2020ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                  const std::shared_ptr<ExternalTexture>& dstTexture,
+                                  const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // The position transform doesn't matter when the reduced shader mode
+                            // in in effect. A matrix transform stage is always included.
+                            .positionTransform = mat4(),
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {0.f, 0.f},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = true}},
+            .alpha = 1.f,
+            .sourceDataspace = kBT2020DataSpace,
+    };
+
+    for (auto alpha : {0.5f, 1.f}) {
+        layer.alpha = alpha;
+        std::vector<LayerSettings> layers;
+        layer.whitePointNits = -1.f;
+        layers.push_back(layer);
+
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+static void drawBT2020ClippedImageLayers(SkiaRenderEngine* renderengine,
+                                         const DisplaySettings& display,
+                                         const std::shared_ptr<ExternalTexture>& dstTexture,
+                                         const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+
+    // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+    // blending instead of EllipticalRRect, so enlarge them a bit.
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    FloatRect boundary(0, 0, displayRect.width(),
+                       displayRect.height() - 10); // boundary is smaller
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .positionTransform = kScaleAsymmetric,
+                            .boundaries = boundary,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {64.1f, 64.1f},
+                    },
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .maxLuminanceNits = 1000.f,
+                                                  .usePremultipliedAlpha = true,
+                                                  .isOpaque = true,
+                                          }},
+            .alpha = 0.5f,
+            .sourceDataspace = kBT2020DataSpace,
+    };
+
+    std::vector<LayerSettings> layers = {layer};
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawExtendedHDRImageLayers(SkiaRenderEngine* renderengine,
+                                       const DisplaySettings& display,
+                                       const std::shared_ptr<ExternalTexture>& dstTexture,
+                                       const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // The position transform doesn't matter when the reduced shader mode
+                            // in in effect. A matrix transform stage is always included.
+                            .positionTransform = mat4(),
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {50.f, 50.f},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = true}},
+            .alpha = 0.5f,
+            .sourceDataspace = kExtendedHdrDataSpce,
+    };
+
+    for (auto roundedCornerRadius : {0.f, 50.f}) {
+        layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+        for (auto alpha : {0.5f, 1.f}) {
+            layer.alpha = alpha;
+            std::vector<LayerSettings> layers;
+
+            for (auto layerWhitePoint : kLayerWhitePoints) {
+                layer.whitePointNits = layerWhitePoint;
+                layers.push_back(layer);
+            }
+            renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+        }
+    }
+}
+
+static void drawP3ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                              const std::shared_ptr<ExternalTexture>& dstTexture,
+                              const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // The position transform doesn't matter when the reduced shader mode
+                            // in in effect. A matrix transform stage is always included.
+                            .positionTransform = mat4(),
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {50.f, 50.f},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = false}},
+            .alpha = 0.5f,
+            .sourceDataspace = kOtherDataSpace,
+    };
+
+    for (auto alpha : {0.5f, 1.f}) {
+        layer.alpha = alpha;
+        std::vector<LayerSettings> layers;
+
+        for (auto layerWhitePoint : kLayerWhitePoints) {
+            layer.whitePointNits = layerWhitePoint;
+            layers.push_back(layer);
+        }
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
 }
 
 //
@@ -335,7 +630,7 @@
 //    kFlushAfterEveryLayer = true
 // in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
 //    gPrintSKSL = true
-void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
+void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) {
     const int previousCount = renderengine->reportShadersCompiled();
     if (previousCount) {
         ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -360,6 +655,23 @@
                 .maxLuminance = 500,
                 .outputDataspace = kOtherDataSpace,
         };
+        DisplaySettings p3DisplayEnhance{.physicalDisplay = displayRect,
+                                         .clip = displayRect,
+                                         .maxLuminance = 500,
+                                         .outputDataspace = kOtherDataSpace,
+                                         .dimmingStage = aidl::android::hardware::graphics::
+                                                 composer3::DimmingStage::GAMMA_OETF,
+                                         .renderIntent = aidl::android::hardware::graphics::
+                                                 composer3::RenderIntent::ENHANCE};
+        DisplaySettings bt2020Display{.physicalDisplay = displayRect,
+                                      .clip = displayRect,
+                                      .maxLuminance = 500,
+                                      .outputDataspace = ui::Dataspace::BT2020,
+                                      .deviceHandlesColorTransform = true,
+                                      .dimmingStage = aidl::android::hardware::graphics::composer3::
+                                              DimmingStage::GAMMA_OETF,
+                                      .renderIntent = aidl::android::hardware::graphics::composer3::
+                                              RenderIntent::TONE_MAP_ENHANCE};
 
         const int64_t usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
 
@@ -384,6 +696,8 @@
                                                impl::ExternalTexture::Usage::WRITEABLE);
         drawHolePunchLayer(renderengine, display, dstTexture);
         drawSolidLayers(renderengine, display, dstTexture);
+        drawSolidLayers(renderengine, p3Display, dstTexture);
+        drawSolidDimmedLayers(renderengine, display, dstTexture);
 
         drawShadowLayers(renderengine, display, srcTexture);
         drawShadowLayers(renderengine, p3Display, srcTexture);
@@ -424,21 +738,45 @@
 
         for (auto texture : textures) {
             drawImageLayers(renderengine, display, dstTexture, texture);
+
+            drawImageDimmedLayers(renderengine, display, dstTexture, texture);
+            drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
+            drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+
             // Draw layers for b/185569240.
             drawClippedLayers(renderengine, display, dstTexture, texture);
         }
 
         drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
 
+        drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+        drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
+        drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
+        drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
+                                         externalTexture);
+
+        drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+        if (shouldPrimeUltraHDR) {
+            drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+            drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+            drawBT2020ImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+
+            drawExtendedHDRImageLayers(renderengine, display, dstTexture, externalTexture);
+            drawExtendedHDRImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+            drawExtendedHDRImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+
+            drawP3ImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+        }
+
         // draw one final layer synchronously to force GL submit
         LayerSettings layer{
                 .source = PixelSource{.solidColor = half3(0.f, 0.f, 0.f)},
         };
         auto layers = std::vector<LayerSettings>{layer};
         // call get() to make it synchronous
-        renderengine
-                ->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd())
-                .get();
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd()).get();
 
         const nsecs_t timeAfter = systemTime();
         const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h
index 437571e..62f6705 100644
--- a/libs/renderengine/skia/Cache.h
+++ b/libs/renderengine/skia/Cache.h
@@ -22,7 +22,7 @@
 
 class Cache {
 public:
-    static void primeShaderCache(SkiaRenderEngine*);
+    static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR);
 
 private:
     Cache() = default;
diff --git a/libs/renderengine/gl/GLExtensions.cpp b/libs/renderengine/skia/GLExtensions.cpp
similarity index 96%
rename from libs/renderengine/gl/GLExtensions.cpp
rename to libs/renderengine/skia/GLExtensions.cpp
index b479400..1d4d35f 100644
--- a/libs/renderengine/gl/GLExtensions.cpp
+++ b/libs/renderengine/skia/GLExtensions.cpp
@@ -23,11 +23,11 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::gl::GLExtensions)
+ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::skia::GLExtensions)
 
 namespace android {
 namespace renderengine {
-namespace gl {
+namespace skia {
 
 namespace {
 
@@ -134,6 +134,6 @@
     return mEGLExtensions.c_str();
 }
 
-} // namespace gl
+} // namespace skia
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/gl/GLExtensions.h b/libs/renderengine/skia/GLExtensions.h
similarity index 98%
rename from libs/renderengine/gl/GLExtensions.h
rename to libs/renderengine/skia/GLExtensions.h
index e415ff3..0cb1bda 100644
--- a/libs/renderengine/gl/GLExtensions.h
+++ b/libs/renderengine/skia/GLExtensions.h
@@ -29,7 +29,7 @@
 
 namespace android {
 namespace renderengine {
-namespace gl {
+namespace skia {
 
 class GLExtensions : public Singleton<GLExtensions> {
 public:
@@ -81,7 +81,7 @@
     GLExtensions& operator=(const GLExtensions&);
 };
 
-} // namespace gl
+} // namespace skia
 } // namespace renderengine
 } // namespace android
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index ff598e7..2053c6a 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -24,8 +24,10 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <android-base/stringprintf.h>
 #include <gl/GrGLInterface.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <gui/TraceUtils.h>
 #include <sync/sync.h>
 #include <ui/DebugUtils.h>
@@ -36,17 +38,26 @@
 #include <memory>
 #include <numeric>
 
-#include "../gl/GLExtensions.h"
+#include "GLExtensions.h"
 #include "log/log_main.h"
 
-bool checkGlError(const char* op, int lineNumber);
-
 namespace android {
 namespace renderengine {
 namespace skia {
 
 using base::StringAppendF;
 
+static bool checkGlError(const char* op, int lineNumber) {
+    bool errorFound = false;
+    GLint error = glGetError();
+    while (error != GL_NO_ERROR) {
+        errorFound = true;
+        error = glGetError();
+        ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
+    }
+    return errorFound;
+}
+
 static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute,
                                          EGLint wanted, EGLConfig* outConfig) {
     EGLint numConfigs = -1, n = 0;
@@ -149,7 +160,7 @@
         LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
     }
 
-    auto& extensions = gl::GLExtensions::getInstance();
+    auto& extensions = GLExtensions::getInstance();
     extensions.initWithEGLStrings(eglVersion, eglExtensions);
 
     // The code assumes that ES2 or later is available if this extension is
@@ -251,14 +262,13 @@
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
-      : SkiaRenderEngine(args.renderEngineType,
-                         static_cast<PixelFormat>(args.pixelFormat),
-                         args.useColorManagement, args.supportsBackgroundBlur),
+      : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
+                         args.supportsBackgroundBlur),
         mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
         mProtectedEGLContext(protectedContext),
-        mProtectedPlaceholderSurface(protectedPlaceholder) { }
+        mProtectedPlaceholderSurface(protectedPlaceholder) {}
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
     finishRenderingAndAbandonContext();
@@ -290,10 +300,10 @@
     LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContext::MakeGL(glInterface, options);
+    contexts.first = GrDirectContexts::MakeGL(glInterface, options);
     if (supportsProtectedContentImpl()) {
         useProtectedContextImpl(GrProtected::kYes);
-        contexts.second = GrDirectContext::MakeGL(glInterface, options);
+        contexts.second = GrDirectContexts::MakeGL(glInterface, options);
         useProtectedContextImpl(GrProtected::kNo);
     }
 
@@ -330,7 +340,8 @@
     } else {
         ATRACE_BEGIN("Submit(sync=false)");
     }
-    bool success = grContext->submit(requireSync);
+    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
+                                                   GrSyncCpu::kNo);
     ATRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
@@ -343,8 +354,8 @@
 }
 
 bool SkiaGLRenderEngine::waitGpuFence(base::borrowed_fd fenceFd) {
-    if (!gl::GLExtensions::getInstance().hasNativeFenceSync() ||
-        !gl::GLExtensions::getInstance().hasWaitSync()) {
+    if (!GLExtensions::getInstance().hasNativeFenceSync() ||
+        !GLExtensions::getInstance().hasWaitSync()) {
         return false;
     }
 
@@ -379,7 +390,7 @@
 
 base::unique_fd SkiaGLRenderEngine::flush() {
     ATRACE_CALL();
-    if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) {
+    if (!GLExtensions::getInstance().hasNativeFenceSync()) {
         return base::unique_fd();
     }
 
@@ -470,13 +481,13 @@
 
 std::optional<RenderEngine::ContextPriority> SkiaGLRenderEngine::createContextPriority(
         const RenderEngineCreationArgs& args) {
-    if (!gl::GLExtensions::getInstance().hasContextPriority()) {
+    if (!GLExtensions::getInstance().hasContextPriority()) {
         return std::nullopt;
     }
 
     switch (args.contextPriority) {
         case RenderEngine::ContextPriority::REALTIME:
-            if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
+            if (GLExtensions::getInstance().hasRealtimePriority()) {
                 return RenderEngine::ContextPriority::REALTIME;
             } else {
                 ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
@@ -520,7 +531,7 @@
 }
 
 void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
-    const gl::GLExtensions& extensions = gl::GLExtensions::getInstance();
+    const GLExtensions& extensions = GLExtensions::getInstance();
     StringAppendF(&result, "\n ------------RE GLES------------\n");
     StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion());
     StringAppendF(&result, "%s\n", extensions.getEGLExtensions());
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index d71e55f..3729be6 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -22,6 +22,7 @@
 
 #include <GrBackendSemaphore.h>
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
@@ -54,6 +55,7 @@
 #include <android-base/stringprintf.h>
 #include <gui/FenceMonitor.h>
 #include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <pthread.h>
 #include <src/core/SkTraceEventCommon.h>
 #include <sync/sync.h>
@@ -241,8 +243,8 @@
 
 using base::StringAppendF;
 
-std::future<void> SkiaRenderEngine::primeCache() {
-    Cache::primeShaderCache(this);
+std::future<void> SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) {
+    Cache::primeShaderCache(this, shouldPrimeUltraHDR);
     return {};
 }
 
@@ -268,10 +270,8 @@
 }
 
 SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat,
-                                   bool useColorManagement, bool supportsBackgroundBlur)
-      : RenderEngine(type),
-        mDefaultPixelFormat(pixelFormat),
-        mUseColorManagement(useColorManagement) {
+                                   bool supportsBackgroundBlur)
+      : RenderEngine(type), mDefaultPixelFormat(pixelFormat) {
     if (supportsBackgroundBlur) {
         ALOGD("Background Blurs Enabled");
         mBlurFilter = new KawaseBlurFilter();
@@ -290,12 +290,12 @@
     }
 
     if (mGrContext) {
-        mGrContext->flushAndSubmit(true);
+        mGrContext->flushAndSubmit(GrSyncCpu::kYes);
         mGrContext->abandonContext();
     }
 
     if (mProtectedGrContext) {
-        mProtectedGrContext->flushAndSubmit(true);
+        mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
         mProtectedGrContext->abandonContext();
     }
 }
@@ -308,7 +308,7 @@
 
     // release any scratch resources before switching into a new mode
     if (getActiveGrContext()) {
-        getActiveGrContext()->purgeUnlockedResources(true);
+        getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
     }
 
     // Backend-specific way to switch to protected context
@@ -400,7 +400,10 @@
     // simply match the existing behavior for protected buffers.)  We also never cache any
     // buffers while in a protected context.
     const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-    if (isProtectedBuffer || isProtected()) {
+    // Don't attempt to map buffers if we're not gpu sampleable. Callers shouldn't send a buffer
+    // over to RenderEngine.
+    const bool isGpuSampleable = buffer->getUsage() & GRALLOC_USAGE_HW_TEXTURE;
+    if (isProtectedBuffer || isProtected() || !isGpuSampleable) {
         return;
     }
     ATRACE_CALL();
@@ -648,8 +651,7 @@
 void SkiaRenderEngine::drawLayersInternal(
         const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-        const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
-        base::unique_fd&& bufferFence) {
+        const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
     ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
@@ -711,9 +713,7 @@
     SkCanvas* canvas = dstCanvas;
     SkiaCapture::OffscreenState offscreenCaptureState;
     const LayerSettings* blurCompositionLayer = nullptr;
-
-    // TODO (b/270314344): Enable blurs in protected context.
-    if (mBlurFilter && !mInProtectedContext) {
+    if (mBlurFilter) {
         bool requiresCompositionLayer = false;
         for (const auto& layer : layers) {
             // if the layer doesn't have blur or it is not visible then continue
@@ -807,8 +807,7 @@
         const auto [bounds, roundRectClip] =
                 getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop,
                                  layer.geometry.roundedCornersRadius);
-        // TODO (b/270314344): Enable blurs in protected context.
-        if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha) && !mInProtectedContext) {
+        if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
             std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
 
             // if multiple layers have blur, then we need to take a snapshot now because
@@ -922,8 +921,7 @@
         // luminance in linear space, which color pipelines request GAMMA_OETF break
         // without a gamma 2.2 fixup.
         const bool requiresLinearEffect = layer.colorTransform != mat4() ||
-                (mUseColorManagement &&
-                 needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
+                (needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
                 (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)) ||
                 (!dimInLinearSpace && isExtendedHdr);
 
@@ -934,10 +932,7 @@
             continue;
         }
 
-        // If color management is disabled, then mark the source image with the same colorspace as
-        // the destination surface so that Skia's color management is a no-op.
-        const ui::Dataspace layerDataspace =
-                !mUseColorManagement ? display.outputDataspace : layer.sourceDataspace;
+        const ui::Dataspace layerDataspace = layer.sourceDataspace;
 
         SkPaint paint;
         if (layer.source.buffer.buffer) {
@@ -1129,7 +1124,7 @@
         }
         if (kFlushAfterEveryLayer) {
             ATRACE_NAME("flush surface");
-            activeSurface->flush();
+            skgpu::ganesh::Flush(activeSurface);
         }
     }
     for (const auto& borderRenderInfo : display.borderInfoList) {
@@ -1157,7 +1152,7 @@
     {
         ATRACE_NAME("flush surface");
         LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
-        activeSurface->flush();
+        skgpu::ganesh::Flush(activeSurface);
     }
 
     auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 723e73c..ac134af 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -59,23 +59,17 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaRenderEngine(RenderEngineType type,
-                     PixelFormat pixelFormat,
-                     bool useColorManagement,
-                     bool supportsBackgroundBlur);
+    SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, bool supportsBackgroundBlur);
     ~SkiaRenderEngine() override;
 
-    std::future<void> primeCache() override final;
+    std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
     void cleanupPostRender() override final;
-    void cleanFramebufferCache() override final{ }
     bool supportsBackgroundBlur() override final {
         return mBlurFilter != nullptr;
     }
     void onActiveDisplaySizeChanged(ui::Size size) override final;
     int reportShadersCompiled();
 
-    virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override final{};
-    virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override final{};
     virtual void setEnableTracing(bool tracingEnabled) override final;
 
     void useProtectedContext(bool useProtectedContext) override;
@@ -142,7 +136,6 @@
                             const DisplaySettings& display,
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
-                            const bool useFramebufferCache,
                             base::unique_fd&& bufferFence) override final;
 
     void dump(std::string& result) override final;
@@ -162,7 +155,6 @@
     sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
     const PixelFormat mDefaultPixelFormat;
-    const bool mUseColorManagement;
 
     // Identifier used for various mappings of layers to various
     // textures or shaders
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index c16586b..8821c0e 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -25,6 +25,7 @@
 #include <GrContextOptions.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 
 #include <android-base/stringprintf.h>
 #include <gui/TraceUtils.h>
@@ -592,7 +593,7 @@
 
 SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
       : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
-                         args.useColorManagement, args.supportsBackgroundBlur) {}
+                         args.supportsBackgroundBlur) {}
 
 SkiaVkRenderEngine::~SkiaVkRenderEngine() {
     finishRenderingAndAbandonContext();
@@ -603,11 +604,11 @@
     sSetupVulkanInterface();
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContext::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+    contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
     if (supportsProtectedContentImpl()) {
         contexts.second =
-                GrDirectContext::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
-                                            options);
+                GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
+                                             options);
     }
 
     return contexts;
@@ -681,7 +682,7 @@
         flushInfo.fFinishedContext = destroySemaphoreInfo;
     }
     GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
-    grContext->submit(false /* no cpu sync */);
+    grContext->submit(GrSyncCpu::kNo);
     int drawFenceFd = -1;
     if (semaphore != VK_NULL_HANDLE) {
         if (GrSemaphoresSubmitted::kYes == submitted) {
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index b21b01c..48dc77e 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -31,14 +31,17 @@
 #include "SkRect.h"
 #include "SkTypeface.h"
 #include "src/utils/SkMultiPictureDocument.h"
+#include <sys/stat.h>
 
 namespace android {
 namespace renderengine {
 namespace skia {
 
 // The root of the filename to write a recorded SKP to. In order for this file to
-// be written to /data/user/, user must run 'adb shell setenforce 0' on the device.
-static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
+// be written, user must run 'adb shell setenforce 0' on the device. Note: This
+// is handled by record.sh. FIXME(b/296282988): With updated selinux policies,
+// 'adb shell setenforce 0' should be unnecessary.
+static const std::string CAPTURED_FILE_DIR = "/data/misc/mskps";
 
 SkiaCapture::~SkiaCapture() {
     mTimer.stop();
@@ -169,11 +172,12 @@
     ATRACE_CALL();
     ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
     base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, "");
-    const std::scoped_lock lock(mMutex);
 
-    // Attach a timestamp to the file.
+    mkdir(CAPTURED_FILE_DIR.c_str(), 0700);
+
+    const std::scoped_lock lock(mMutex);
     mCaptureFile.clear();
-    base::StringAppendF(&mCaptureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+    base::StringAppendF(&mCaptureFile, "%s/re_skiacapture_%lld.mskp", CAPTURED_FILE_DIR.c_str(),
                         std::chrono::steady_clock::now().time_since_epoch().count());
     auto stream = std::make_unique<SkFILEWStream>(mCaptureFile.c_str());
     // We own this stream and need to hold it until close() finishes.
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
index f24a4f1..5bf6560 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
@@ -18,7 +18,6 @@
 
 #include "SkiaMemoryReporter.h"
 
-#include <SkString.h>
 #include <android-base/stringprintf.h>
 #include <log/log_main.h>
 
@@ -142,7 +141,7 @@
                 TraceValue traceValue = convertUnits(result->second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
                 StringAppendF(&log, "  %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
-                              traceValue.value, traceValue.units, traceValue.count, entry);
+                              traceValue.value, traceValue.units.c_str(), traceValue.count, entry);
             }
             if (mItemize) {
                 for (const auto& individualItem : resultsMap) {
@@ -153,7 +152,7 @@
                         auto result = individualItem.second.find("size");
                         TraceValue size = convertUnits(result->second);
                         StringAppendF(&log, "    %s: size[%.2f %s]", individualItem.first.c_str(),
-                                      size.value, size.units);
+                                      size.value, size.units.c_str());
                         if (!wrappedResources) {
                             for (const auto& itemValues : individualItem.second) {
                                 if (strcmp("size", itemValues.first) == 0) {
@@ -162,10 +161,10 @@
                                 TraceValue traceValue = convertUnits(itemValues.second);
                                 if (traceValue.value == 0.0f) {
                                     StringAppendF(&log, " %s[%s]", itemValues.first,
-                                                  traceValue.units);
+                                                  traceValue.units.c_str());
                                 } else {
                                     StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
-                                                  traceValue.value, traceValue.units);
+                                                  traceValue.value, traceValue.units.c_str());
                                 }
                             }
                         }
@@ -184,16 +183,16 @@
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
     StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
-                  total.value, total.units, purgeable.value, purgeable.units);
+                  total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
 }
 
 SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
     TraceValue output(value);
-    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("bytes") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "KB";
     }
-    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("KB") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "MB";
     }
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.h b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
index dbbd65b..da91674 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.h
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <SkString.h>
 #include <SkTraceMemoryDump.h>
 
 #include <string>
@@ -75,7 +76,7 @@
         TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
         TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
 
-        const char* units;
+        SkString units;
         float value;
         int count;
     };
@@ -104,4 +105,4 @@
 
 } /* namespace skia */
 } /* namespace renderengine */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/renderengine/skia/debug/record.sh b/libs/renderengine/skia/debug/record.sh
index e99b7ae..88d8b09 100755
--- a/libs/renderengine/skia/debug/record.sh
+++ b/libs/renderengine/skia/debug/record.sh
@@ -16,7 +16,6 @@
   # first time use requires these changes
   adb root
   adb shell setenforce 0
-  adb shell setprop debug.renderengine.backend "skiaglthreaded"
   adb shell stop
   adb shell start
   exit 1;
@@ -44,7 +43,6 @@
 # There is no guarantee that at least one frame passed through renderengine during that time
 # but as far as I know it always at least writes a 0-byte file with a new name, unless it crashes
 # the process it is recording.
-# /data/user/re_skiacapture_56204430551705.mskp
 
 spin() {
     case "$spin" in
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index 2557ac9..1e0c4cf 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -16,6 +16,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 #include "BlurFilter.h"
+#include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkPaint.h>
 #include <SkRRect.h>
@@ -23,6 +24,7 @@
 #include <SkSize.h>
 #include <SkString.h>
 #include <SkSurface.h>
+#include <SkTileMode.h>
 #include <log/log.h>
 #include <utils/Trace.h>
 
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index 511d7c9..e72c501 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -17,6 +17,7 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "GaussianBlurFilter.h"
+#include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkPaint.h>
 #include <SkRRect.h>
@@ -25,6 +26,8 @@
 #include <SkSize.h>
 #include <SkString.h>
 #include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
 #include <log/log.h>
 #include <utils/Trace.h>
@@ -45,8 +48,8 @@
     // Create blur surface with the bit depth and colorspace of the original surface
     SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
                                                        std::ceil(blurRect.height() * kInputScale));
-    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context,
-                                                           skgpu::Budgeted::kNo, scaledInfo);
+    sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context,
+                                                        skgpu::Budgeted::kNo, scaledInfo);
 
     SkPaint paint;
     paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index e370c39..5c9820c 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -17,13 +17,20 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "KawaseBlurFilter.h"
+#include <SkAlphaType.h>
+#include <SkBlendMode.h>
 #include <SkCanvas.h>
+#include <SkImageInfo.h>
 #include <SkPaint.h>
 #include <SkRRect.h>
 #include <SkRuntimeEffect.h>
+#include <SkShader.h>
 #include <SkSize.h>
 #include <SkString.h>
 #include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <log/log.h>
 #include <utils/Trace.h>
 
@@ -32,19 +39,18 @@
 namespace skia {
 
 KawaseBlurFilter::KawaseBlurFilter(): BlurFilter() {
-    SkString blurString(R"(
-        uniform shader child;
-        uniform float in_blurOffset;
+    SkString blurString(
+        "uniform shader child;"
+        "uniform float in_blurOffset;"
 
-        half4 main(float2 xy) {
-            half4 c = child.eval(xy);
-            c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));
-            c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));
-            c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));
-            c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));
-            return half4(c.rgb * 0.2, 1.0);
-        }
-    )");
+        "half4 main(float2 xy) {"
+            "half4 c = child.eval(xy);"
+            "c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));"
+            "c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));"
+            "c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));"
+            "c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));"
+            "return half4(c.rgb * 0.2, 1.0);"
+        "}");
 
     auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
     if (!blurEffect) {
@@ -53,14 +59,36 @@
     mBlurEffect = std::move(blurEffect);
 }
 
-sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
-                                          const sk_sp<SkImage> input, const SkRect& blurRect)
-    const {
+// Draws the given runtime shader on a GPU (Ganesh) surface and returns the result as an
+// SkImage.
+static sk_sp<SkImage> makeImage(SkSurface* surface, SkRuntimeShaderBuilder* builder) {
+    sk_sp<SkShader> shader = builder->makeShader(nullptr);
+    if (!shader) {
+        return nullptr;
+    }
+    SkPaint paint;
+    paint.setShader(std::move(shader));
+    paint.setBlendMode(SkBlendMode::kSrc);
+    surface->getCanvas()->drawPaint(paint);
+    return surface->makeImageSnapshot();
+}
+
+sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context,
+                                          const uint32_t blurRadius,
+                                          const sk_sp<SkImage> input,
+                                          const SkRect& blurRect) const {
+    LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__);
+    LOG_ALWAYS_FATAL_IF(input == nullptr, "%s: Invalid input image", __func__);
+
+    if (blurRadius == 0) {
+        return input;
+    }
+
     // Kawase is an approximation of Gaussian, but it behaves differently from it.
     // A radius transformation is required for approximating them, and also to introduce
     // non-integer steps, necessary to smoothly interpolate large radii.
     float tmpRadius = (float)blurRadius / 2.0f;
-    float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+    uint32_t numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
     float radiusByPasses = tmpRadius / (float)numberOfPasses;
 
     // create blur surface with the bit depth and colorspace of the original surface
@@ -80,14 +108,33 @@
             input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
     blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
 
-    sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
+    constexpr int kSampleCount = 1;
+    constexpr bool kMipmapped = false;
+    constexpr SkSurfaceProps* kProps = nullptr;
+    sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo,
+                                                        kSampleCount, kTopLeft_GrSurfaceOrigin,
+                                                        kProps, kMipmapped, input->isProtected());
+    LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
+    sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
 
-    // And now we'll build our chain of scaled blur stages
-    for (auto i = 1; i < numberOfPasses; i++) {
-        blurBuilder.child("child") =
-                tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
-        blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
-        tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
+    // And now we'll build our chain of scaled blur stages. If there is more than one pass,
+    // create a second surface and ping pong between them.
+    sk_sp<SkSurface> surfaceTwo;
+    if (numberOfPasses <= 1) {
+        LOG_ALWAYS_FATAL_IF(tmpBlur == nullptr, "%s: tmpBlur is null", __func__);
+    } else {
+        surfaceTwo = surface->makeSurface(scaledInfo);
+        LOG_ALWAYS_FATAL_IF(!surfaceTwo, "%s: Failed to create second blur surface!", __func__);
+
+        for (auto i = 1; i < numberOfPasses; i++) {
+            LOG_ALWAYS_FATAL_IF(tmpBlur == nullptr, "%s: tmpBlur is null for pass %d", __func__, i);
+            blurBuilder.child("child") =
+                    tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+            blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
+            tmpBlur = makeImage(surfaceTwo.get(), &blurBuilder);
+            using std::swap;
+            swap(surface, surfaceTwo);
+        }
     }
 
     return tmpBlur;
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index f3f2da8..11d4fde 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -109,7 +109,6 @@
     virtual renderengine::RenderEngine::RenderEngineType type() = 0;
     virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0;
     virtual bool typeSupported() = 0;
-    virtual bool useColorManagement() const = 0;
 };
 
 class SkiaVkRenderEngineFactory : public RenderEngineFactory {
@@ -130,13 +129,11 @@
                 renderengine::RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
                         .setImageCacheSize(1)
-                        .setUseColorManagerment(false)
                         .setEnableProtectedContext(false)
                         .setPrecacheToneMapperShaderOnly(false)
                         .setSupportsBackgroundBlur(true)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
                         .setRenderEngineType(type())
-                        .setUseColorManagerment(useColorManagement())
                         .build();
         return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs);
     }
@@ -144,14 +141,9 @@
     bool typeSupported() override {
         return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine();
     }
-    bool useColorManagement() const override { return false; }
     void skip() { GTEST_SKIP(); }
 };
 
-class SkiaVkCMRenderEngineFactory : public SkiaVkRenderEngineFactory {
-public:
-    bool useColorManagement() const override { return true; }
-};
 class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "SkiaGLRenderEngineFactory"; }
@@ -170,13 +162,11 @@
                         .setSupportsBackgroundBlur(true)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
                         .setRenderEngineType(type())
-                        .setUseColorManagerment(useColorManagement())
                         .build();
         return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
     }
 
     bool typeSupported() override { return true; }
-    bool useColorManagement() const override { return false; }
 };
 
 class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
@@ -197,13 +187,11 @@
                         .setSupportsBackgroundBlur(true)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
                         .setRenderEngineType(type())
-                        .setUseColorManagerment(useColorManagement())
                         .build();
         return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
     }
 
     bool typeSupported() override { return true; }
-    bool useColorManagement() const override { return true; }
 };
 
 class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
@@ -290,9 +278,6 @@
         if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
             writeBufferToFile("/data/texture_out_");
         }
-        for (uint32_t texName : mTexNames) {
-            mRE->deleteTextures(1, &texName);
-        }
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
         ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
@@ -418,7 +403,7 @@
     }
 
     void expectShadowColor(const renderengine::LayerSettings& castingLayer,
-                           const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
+                           const ShadowSettings& shadow, const ubyte4& casterColor,
                            const ubyte4& backgroundColor) {
         const Rect casterRect(castingLayer.geometry.boundaries);
         Region casterRegion = Region(casterRect);
@@ -458,8 +443,7 @@
                           backgroundColor.a);
     }
 
-    void expectShadowColorWithoutCaster(const FloatRect& casterBounds,
-                                        const renderengine::ShadowSettings& shadow,
+    void expectShadowColorWithoutCaster(const FloatRect& casterBounds, const ShadowSettings& shadow,
                                         const ubyte4& backgroundColor) {
         const float shadowInset = shadow.length * -1.0f;
         const Rect casterRect(casterBounds);
@@ -478,9 +462,9 @@
                           backgroundColor.a);
     }
 
-    static renderengine::ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
-                                                          bool casterIsTranslucent) {
-        renderengine::ShadowSettings shadow;
+    static ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
+                                            bool casterIsTranslucent) {
+        ShadowSettings shadow;
         shadow.ambientColor = {0.0f, 0.0f, 0.0f, 0.039f};
         shadow.spotColor = {0.0f, 0.0f, 0.0f, 0.19f};
         shadow.lightPos = vec3(casterPos.x, casterPos.y, 0);
@@ -505,7 +489,7 @@
     void invokeDraw(const renderengine::DisplaySettings& settings,
                     const std::vector<renderengine::LayerSettings>& layers) {
         ftl::Future<FenceResult> future =
-                mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+                mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
         ASSERT_TRUE(future.valid());
 
         auto result = future.get();
@@ -617,12 +601,10 @@
     void fillGreenColorBufferThenClearRegion();
 
     template <typename SourceVariant>
-    void drawShadow(const renderengine::LayerSettings& castingLayer,
-                    const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
-                    const ubyte4& backgroundColor);
+    void drawShadow(const renderengine::LayerSettings& castingLayer, const ShadowSettings& shadow,
+                    const ubyte4& casterColor, const ubyte4& backgroundColor);
 
-    void drawShadowWithoutCaster(const FloatRect& castingBounds,
-                                 const renderengine::ShadowSettings& shadow,
+    void drawShadowWithoutCaster(const FloatRect& castingBounds, const ShadowSettings& shadow,
                                  const ubyte4& backgroundColor);
 
     // Tonemaps grey values from sourceDataspace -> Display P3 and checks that GPU and CPU
@@ -635,8 +617,6 @@
 
     std::unique_ptr<renderengine::RenderEngine> mRE;
     std::shared_ptr<renderengine::ExternalTexture> mBuffer;
-
-    std::vector<uint32_t> mTexNames;
 };
 
 void RenderEngineTest::initializeRenderEngine() {
@@ -680,9 +660,6 @@
     static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
                           RenderEngineTest* fixture) {
         const auto buf = fixture->allocateSourceBuffer(1, 1);
-        uint32_t texName;
-        fixture->mRE->genTextures(1, &texName);
-        fixture->mTexNames.push_back(texName);
 
         uint8_t* pixels;
         buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -702,7 +679,6 @@
         buf->getBuffer()->unlock();
 
         layer.source.buffer.buffer = buf;
-        layer.source.buffer.textureName = texName;
         layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
         OpaquenessVariant::setOpaqueBit(layer);
     }
@@ -1251,9 +1227,6 @@
     // Here will allocate a checker board texture, but transform texture
     // coordinates so that only the upper left is applied.
     const auto buf = allocateSourceBuffer(2, 2);
-    uint32_t texName;
-    RenderEngineTest::mRE->genTextures(1, &texName);
-    this->mTexNames.push_back(texName);
 
     uint8_t* pixels;
     buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1274,7 +1247,6 @@
     buf->getBuffer()->unlock();
 
     layer.source.buffer.buffer = buf;
-    layer.source.buffer.textureName = texName;
     // Transform coordinates to only be inside the red quadrant.
     layer.source.buffer.textureTransform = mat4::scale(vec4(0.2f, 0.2f, 1.f, 1.f));
     layer.alpha = 1.0f;
@@ -1300,9 +1272,6 @@
 
     renderengine::LayerSettings layer;
     const auto buf = allocateSourceBuffer(1, 1);
-    uint32_t texName;
-    RenderEngineTest::mRE->genTextures(1, &texName);
-    this->mTexNames.push_back(texName);
 
     uint8_t* pixels;
     buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1314,7 +1283,6 @@
     buf->getBuffer()->unlock();
 
     layer.source.buffer.buffer = buf;
-    layer.source.buffer.textureName = texName;
     layer.source.buffer.usePremultipliedAlpha = true;
     layer.alpha = 0.5f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -1339,9 +1307,6 @@
 
     renderengine::LayerSettings layer;
     const auto buf = allocateSourceBuffer(1, 1);
-    uint32_t texName;
-    RenderEngineTest::mRE->genTextures(1, &texName);
-    this->mTexNames.push_back(texName);
 
     uint8_t* pixels;
     buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1353,7 +1318,6 @@
     buf->getBuffer()->unlock();
 
     layer.source.buffer.buffer = buf;
-    layer.source.buffer.textureName = texName;
     layer.source.buffer.usePremultipliedAlpha = false;
     layer.alpha = 0.5f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -1370,8 +1334,8 @@
 
 template <typename SourceVariant>
 void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLayer,
-                                  const renderengine::ShadowSettings& shadow,
-                                  const ubyte4& casterColor, const ubyte4& backgroundColor) {
+                                  const ShadowSettings& shadow, const ubyte4& casterColor,
+                                  const ubyte4& backgroundColor) {
     renderengine::DisplaySettings settings;
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     settings.physicalDisplay = fullscreenRect();
@@ -1407,7 +1371,7 @@
 }
 
 void RenderEngineTest::drawShadowWithoutCaster(const FloatRect& castingBounds,
-                                               const renderengine::ShadowSettings& shadow,
+                                               const ShadowSettings& shadow,
                                                const ubyte4& backgroundColor) {
     renderengine::DisplaySettings settings;
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1559,9 +1523,7 @@
 
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
-                                         std::make_shared<SkiaGLESCMRenderEngineFactory>(),
-                                         std::make_shared<SkiaVkRenderEngineFactory>(),
-                                         std::make_shared<SkiaVkCMRenderEngineFactory>()));
+                                         std::make_shared<SkiaVkRenderEngineFactory>()));
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
     if (!GetParam()->typeSupported()) {
@@ -1645,8 +1607,7 @@
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layers.push_back(layer);
-    ftl::Future<FenceResult> future =
-            mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd());
+    ftl::Future<FenceResult> future = mRE->drawLayers(settings, layers, nullptr, base::unique_fd());
 
     ASSERT_TRUE(future.valid());
     auto result = future.get();
@@ -1745,7 +1706,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -1756,7 +1717,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -1895,7 +1856,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -1906,7 +1867,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -2045,7 +2006,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -2056,7 +2017,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -2139,9 +2100,8 @@
     const float shadowLength = 5.0f;
     Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
-    renderengine::ShadowSettings settings =
-            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
-                              false /* casterIsTranslucent */);
+    ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+                                                shadowLength, false /* casterIsTranslucent */);
 
     drawShadowWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
     expectShadowColorWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
@@ -2163,9 +2123,8 @@
     renderengine::LayerSettings castingLayer;
     castingLayer.geometry.boundaries = casterBounds.toFloatRect();
     castingLayer.alpha = 1.0f;
-    renderengine::ShadowSettings settings =
-            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
-                              false /* casterIsTranslucent */);
+    ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+                                                shadowLength, false /* casterIsTranslucent */);
 
     drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
     expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
@@ -2188,9 +2147,8 @@
     castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     castingLayer.geometry.boundaries = casterBounds.toFloatRect();
     castingLayer.alpha = 1.0f;
-    renderengine::ShadowSettings settings =
-            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
-                              false /* casterIsTranslucent */);
+    ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+                                                shadowLength, false /* casterIsTranslucent */);
 
     drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
     expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
@@ -2213,9 +2171,8 @@
     castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     castingLayer.geometry.boundaries = casterBounds.toFloatRect();
     castingLayer.alpha = 1.0f;
-    renderengine::ShadowSettings settings =
-            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
-                              false /* casterIsTranslucent */);
+    ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+                                                shadowLength, false /* casterIsTranslucent */);
 
     drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
                                                               backgroundColor);
@@ -2240,9 +2197,8 @@
     castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f};
     castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect();
     castingLayer.alpha = 1.0f;
-    renderengine::ShadowSettings settings =
-            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
-                              false /* casterIsTranslucent */);
+    ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+                                                shadowLength, false /* casterIsTranslucent */);
 
     drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
                                                               backgroundColor);
@@ -2263,9 +2219,8 @@
     renderengine::LayerSettings castingLayer;
     castingLayer.geometry.boundaries = casterBounds.toFloatRect();
     castingLayer.alpha = 0.5f;
-    renderengine::ShadowSettings settings =
-            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
-                              true /* casterIsTranslucent */);
+    ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+                                                shadowLength, true /* casterIsTranslucent */);
 
     drawShadow<BufferSourceVariant<RelaxOpaqueBufferVariant>>(castingLayer, settings, casterColor,
                                                               backgroundColor);
@@ -2298,14 +2253,14 @@
     layers.push_back(layer);
 
     ftl::Future<FenceResult> futureOne =
-            mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+            mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
     ASSERT_TRUE(futureOne.valid());
     auto resultOne = futureOne.get();
     ASSERT_TRUE(resultOne.ok());
     auto fenceOne = resultOne.value();
 
     ftl::Future<FenceResult> futureTwo =
-            mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(fenceOne->dup()));
+            mRE->drawLayers(settings, layers, mBuffer, base::unique_fd(fenceOne->dup()));
     ASSERT_TRUE(futureTwo.valid());
     auto resultTwo = futureTwo.get();
     ASSERT_TRUE(resultTwo.ok());
@@ -2592,10 +2547,6 @@
         GTEST_SKIP();
     }
 
-    if (!GetParam()->useColorManagement()) {
-        GTEST_SKIP();
-    }
-
     initializeRenderEngine();
 
     const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
@@ -3017,15 +2968,11 @@
     std::vector<renderengine::LayerSettings> layers{greenLayer};
     invokeDraw(display, layers);
 
-    if (GetParam()->useColorManagement()) {
-        expectBufferColor(rect, 117, 251, 76, 255);
-    } else {
-        expectBufferColor(rect, 0, 255, 0, 255);
-    }
+    expectBufferColor(rect, 117, 251, 76, 255);
 }
 
 TEST_P(RenderEngineTest, test_tonemapPQMatches) {
-    if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) {
+    if (!GetParam()->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -3042,7 +2989,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
-    if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) {
+    if (!GetParam()->typeSupported()) {
         GTEST_SKIP();
     }
 
@@ -3257,14 +3204,14 @@
     }
     initializeRenderEngine();
 
-    auto fut = mRE->primeCache();
+    auto fut = mRE->primeCache(false);
     if (fut.valid()) {
         fut.wait();
     }
 
-    const int minimumExpectedShadersCompiled = GetParam()->useColorManagement() ? 60 : 30;
+    static constexpr int kMinimumExpectedShadersCompiled = 60;
     ASSERT_GT(static_cast<skia::SkiaGLRenderEngine*>(mRE.get())->reportShadersCompiled(),
-              minimumExpectedShadersCompiled);
+              kMinimumExpectedShadersCompiled);
 }
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index fe3a16d..1b9adba 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -36,7 +36,7 @@
     void SetUp() override {
         mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
                 [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
-                renderengine::RenderEngine::RenderEngineType::THREADED);
+                renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED);
     }
 
     std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
@@ -50,25 +50,13 @@
 }
 
 TEST_F(RenderEngineThreadedTest, primeCache) {
-    EXPECT_CALL(*mRenderEngine, primeCache());
-    mThreadedRE->primeCache();
+    EXPECT_CALL(*mRenderEngine, primeCache(false));
+    mThreadedRE->primeCache(false);
     // need to call ANY synchronous function after primeCache to ensure that primeCache has
     // completed asynchronously before the test completes execution.
     mThreadedRE->getContextPriority();
 }
 
-TEST_F(RenderEngineThreadedTest, genTextures) {
-    uint32_t texName;
-    EXPECT_CALL(*mRenderEngine, genTextures(1, &texName));
-    mThreadedRE->genTextures(1, &texName);
-}
-
-TEST_F(RenderEngineThreadedTest, deleteTextures) {
-    uint32_t texName;
-    EXPECT_CALL(*mRenderEngine, deleteTextures(1, &texName));
-    mThreadedRE->deleteTextures(1, &texName);
-}
-
 TEST_F(RenderEngineThreadedTest, getMaxTextureSize_returns20) {
     size_t size = 20;
     EXPECT_CALL(*mRenderEngine, getMaxTextureSize()).WillOnce(Return(size));
@@ -110,7 +98,6 @@
 }
 
 TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
-    EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true));
     EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0);
     mThreadedRE->cleanupPostRender();
 
@@ -119,8 +106,25 @@
 }
 
 TEST_F(RenderEngineThreadedTest, PostRenderCleanup_notSkipped) {
-    EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(false));
+    renderengine::DisplaySettings settings;
+    std::vector<renderengine::LayerSettings> layers;
+    std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(), *mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    base::unique_fd bufferFence;
+
+    EXPECT_CALL(*mRenderEngine, useProtectedContext(false));
+    EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+        .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                          const renderengine::DisplaySettings&,
+                          const std::vector<renderengine::LayerSettings>&,
+                          const std::shared_ptr<renderengine::ExternalTexture>&,
+                          base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
     EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return());
+    ftl::Future<FenceResult> future =
+            mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
     mThreadedRE->cleanupPostRender();
 
     // call ANY synchronous function to ensure that cleanupPostRender has completed.
@@ -155,11 +159,11 @@
             .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
                           const renderengine::DisplaySettings&,
                           const std::vector<renderengine::LayerSettings>&,
-                          const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                          const std::shared_ptr<renderengine::ExternalTexture>&,
                           base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
 
     ftl::Future<FenceResult> future =
-            mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+            mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
     ASSERT_TRUE(future.valid());
     auto result = future.get();
     ASSERT_TRUE(result.ok());
@@ -188,11 +192,11 @@
             .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
                           const renderengine::DisplaySettings&,
                           const std::vector<renderengine::LayerSettings>&,
-                          const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                          const std::shared_ptr<renderengine::ExternalTexture>&,
                           base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
 
     ftl::Future<FenceResult> future =
-            mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+            mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
     ASSERT_TRUE(future.valid());
     auto result = future.get();
     ASSERT_TRUE(result.ok());
@@ -216,11 +220,11 @@
             .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
                           const renderengine::DisplaySettings&,
                           const std::vector<renderengine::LayerSettings>&,
-                          const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                          const std::shared_ptr<renderengine::ExternalTexture>&,
                           base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
 
     ftl::Future<FenceResult> future =
-            mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+            mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
     ASSERT_TRUE(future.valid());
     auto result = future.get();
     ASSERT_TRUE(result.ok());
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 6a1561a..367bee8 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -27,8 +27,6 @@
 #include <processgroup/processgroup.h>
 #include <utils/Trace.h>
 
-#include "gl/GLESRenderEngine.h"
-
 using namespace std::chrono_literals;
 
 namespace android {
@@ -131,7 +129,7 @@
     mInitializedCondition.wait(lock, [=] { return mIsInitialized; });
 }
 
-std::future<void> RenderEngineThreaded::primeCache() {
+std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
     const auto resultPromise = std::make_shared<std::promise<void>>();
     std::future<void> resultFuture = resultPromise->get_future();
     ATRACE_CALL();
@@ -139,19 +137,20 @@
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([resultPromise](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::primeCache");
-            if (setSchedFifo(false) != NO_ERROR) {
-                ALOGW("Couldn't set SCHED_OTHER for primeCache");
-            }
+        mFunctionCalls.push(
+                [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) {
+                    ATRACE_NAME("REThreaded::primeCache");
+                    if (setSchedFifo(false) != NO_ERROR) {
+                        ALOGW("Couldn't set SCHED_OTHER for primeCache");
+                    }
 
-            instance.primeCache();
-            resultPromise->set_value();
+                    instance.primeCache(shouldPrimeUltraHDR);
+                    resultPromise->set_value();
 
-            if (setSchedFifo(true) != NO_ERROR) {
-                ALOGW("Couldn't set SCHED_FIFO for primeCache");
-            }
-        });
+                    if (setSchedFifo(true) != NO_ERROR) {
+                        ALOGW("Couldn't set SCHED_FIFO for primeCache");
+                    }
+                });
     }
     mCondition.notify_one();
 
@@ -175,46 +174,6 @@
     result.assign(resultFuture.get());
 }
 
-void RenderEngineThreaded::genTextures(size_t count, uint32_t* names) {
-    ATRACE_CALL();
-    // This is a no-op in SkiaRenderEngine.
-    if (getRenderEngineType() != RenderEngineType::THREADED) {
-        return;
-    }
-    std::promise<void> resultPromise;
-    std::future<void> resultFuture = resultPromise.get_future();
-    {
-        std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([&resultPromise, count, names](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::genTextures");
-            instance.genTextures(count, names);
-            resultPromise.set_value();
-        });
-    }
-    mCondition.notify_one();
-    resultFuture.wait();
-}
-
-void RenderEngineThreaded::deleteTextures(size_t count, uint32_t const* names) {
-    ATRACE_CALL();
-    // This is a no-op in SkiaRenderEngine.
-    if (getRenderEngineType() != RenderEngineType::THREADED) {
-        return;
-    }
-    std::promise<void> resultPromise;
-    std::future<void> resultFuture = resultPromise.get_future();
-    {
-        std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([&resultPromise, count, &names](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::deleteTextures");
-            instance.deleteTextures(count, names);
-            resultPromise.set_value();
-        });
-    }
-    mCondition.notify_one();
-    resultFuture.wait();
-}
-
 void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                                     bool isRenderable) {
     ATRACE_CALL();
@@ -273,60 +232,45 @@
             ATRACE_NAME("REThreaded::cleanupPostRender");
             instance.cleanupPostRender();
         });
+        mNeedsPostRenderCleanup = false;
     }
     mCondition.notify_one();
 }
 
 bool RenderEngineThreaded::canSkipPostRenderCleanup() const {
-    waitUntilInitialized();
-    return mRenderEngine->canSkipPostRenderCleanup();
+    return !mNeedsPostRenderCleanup;
 }
 
 void RenderEngineThreaded::drawLayersInternal(
         const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
-        base::unique_fd&& bufferFence) {
+        const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
     resultPromise->set_value(Fence::NO_FENCE);
     return;
 }
 
 ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
-        base::unique_fd&& bufferFence) {
+        const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
     ATRACE_CALL();
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
     int fd = bufferFence.release();
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache,
-                             fd](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::drawLayers");
-            instance.updateProtectedContext(layers, buffer);
-            instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
-                                        useFramebufferCache, base::unique_fd(fd));
-        });
+        mNeedsPostRenderCleanup = true;
+        mFunctionCalls.push(
+                [resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
+                    ATRACE_NAME("REThreaded::drawLayers");
+                    instance.updateProtectedContext(layers, buffer);
+                    instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
+                                                base::unique_fd(fd));
+                });
     }
     mCondition.notify_one();
     return resultFuture;
 }
 
-void RenderEngineThreaded::cleanFramebufferCache() {
-    ATRACE_CALL();
-    // This function is designed so it can run asynchronously, so we do not need to wait
-    // for the futures.
-    {
-        std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::cleanFramebufferCache");
-            instance.cleanFramebufferCache();
-        });
-    }
-    mCondition.notify_one();
-}
-
 int RenderEngineThreaded::getContextPriority() {
     std::promise<int> resultPromise;
     std::future<int> resultFuture = resultPromise.get_future();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 6eb108e..74af2bd 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -42,12 +42,10 @@
 
     RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
     ~RenderEngineThreaded() override;
-    std::future<void> primeCache() override;
+    std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
 
     void dump(std::string& result) override;
 
-    void genTextures(size_t count, uint32_t* names) override;
-    void deleteTextures(size_t count, uint32_t const* names) override;
     size_t getMaxTextureSize() const override;
     size_t getMaxViewportDims() const override;
 
@@ -57,10 +55,8 @@
     ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
                                         const std::vector<LayerSettings>& layers,
                                         const std::shared_ptr<ExternalTexture>& buffer,
-                                        const bool useFramebufferCache,
                                         base::unique_fd&& bufferFence) override;
 
-    void cleanFramebufferCache() override;
     int getContextPriority() override;
     bool supportsBackgroundBlur() override;
     void onActiveDisplaySizeChanged(ui::Size size) override;
@@ -75,7 +71,7 @@
                             const DisplaySettings& display,
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
-                            const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
+                            base::unique_fd&& bufferFence) override;
 
 private:
     void threadMain(CreateInstanceFactory factory);
@@ -93,6 +89,7 @@
     mutable std::mutex mThreadMutex;
     std::thread mThread GUARDED_BY(mThreadMutex);
     std::atomic<bool> mRunning = true;
+    std::atomic<bool> mNeedsPostRenderCleanup = false;
 
     using Work = std::function<void(renderengine::RenderEngine&)>;
     mutable std::queue<Work> mFunctionCalls GUARDED_BY(mThreadMutex);
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index b6b9cc4..d992aa5 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -49,6 +49,7 @@
         "liblog",
         "libhardware",
         "libpermission",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
     ],
 
     export_include_dirs: ["include"],
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 019d6cb..12f600e 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -43,6 +43,8 @@
     CREATE_SENSOR_DIRECT_CONNECTION,
     SET_OPERATION_PARAMETER,
     GET_RUNTIME_SENSOR_LIST,
+    ENABLE_REPLAY_DATA_INJECTION,
+    ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION,
 };
 
 class BpSensorServer : public BpInterface<ISensorServer>
@@ -64,6 +66,14 @@
         Sensor s;
         Vector<Sensor> v;
         uint32_t n = reply.readUint32();
+        // The size of the n Sensor elements on the wire is what we really want, but
+        // this is better than nothing.
+        if (n > reply.dataAvail()) {
+            ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+                  "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+                  n, reply.dataAvail());
+            return v;
+        }
         v.setCapacity(n);
         while (n) {
             n--;
@@ -86,6 +96,14 @@
         Sensor s;
         Vector<Sensor> v;
         uint32_t n = reply.readUint32();
+        // The size of the n Sensor elements on the wire is what we really want, but
+        // this is better than nothing.
+        if (n > reply.dataAvail()) {
+            ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+                  "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+                  n, reply.dataAvail());
+            return v;
+        }
         v.setCapacity(n);
         while (n) {
             n--;
@@ -109,6 +127,14 @@
         Sensor s;
         Vector<Sensor> v;
         uint32_t n = reply.readUint32();
+        // The size of the n Sensor elements on the wire is what we really want, but
+        // this is better than nothing.
+        if (n > reply.dataAvail()) {
+            ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+                  "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+                  n, reply.dataAvail());
+            return v;
+        }
         v.setCapacity(n);
         while (n) {
             n--;
@@ -138,6 +164,20 @@
         return reply.readInt32();
     }
 
+    virtual int isReplayDataInjectionEnabled() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+        remote()->transact(ENABLE_REPLAY_DATA_INJECTION, data, &reply);
+        return reply.readInt32();
+    }
+
+    virtual int isHalBypassReplayDataInjectionEnabled() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+        remote()->transact(ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION, data, &reply);
+        return reply.readInt32();
+    }
+
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             int deviceId, uint32_t size, int32_t type, int32_t format,
             const native_handle_t *resource) {
@@ -213,6 +253,18 @@
             reply->writeInt32(static_cast<int32_t>(ret));
             return NO_ERROR;
         }
+        case ENABLE_REPLAY_DATA_INJECTION: {
+            CHECK_INTERFACE(ISensorServer, data, reply);
+            int32_t ret = isReplayDataInjectionEnabled();
+            reply->writeInt32(static_cast<int32_t>(ret));
+            return NO_ERROR;
+        }
+        case ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION: {
+            CHECK_INTERFACE(ISensorServer, data, reply);
+            int32_t ret = isHalBypassReplayDataInjectionEnabled();
+            reply->writeInt32(static_cast<int32_t>(ret));
+            return NO_ERROR;
+        }
         case GET_DYNAMIC_SENSOR_LIST: {
             CHECK_INTERFACE(ISensorServer, data, reply);
             const String16& opPackageName = data.readString16();
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 980f8d1..d112a12 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -26,6 +26,8 @@
 #include <utils/RefBase.h>
 #include <utils/Singleton.h>
 
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
+
 #include <binder/IBinder.h>
 #include <binder/IPermissionController.h>
 #include <binder/IServiceManager.h>
@@ -39,6 +41,43 @@
 namespace android {
 // ----------------------------------------------------------------------------
 
+namespace {
+
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
+
+static constexpr int DEVICE_ID_DEFAULT = 0;
+
+// Returns the deviceId of the device where this uid is observed. If the uid is present on more than
+// one devices, return the default deviceId.
+int getDeviceIdForUid(uid_t uid) {
+    sp<IBinder> binder =
+            defaultServiceManager()->checkService(String16("virtualdevice_native"));
+    if (binder != nullptr) {
+        auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+        std::vector<int> deviceIds;
+        vdm->getDeviceIdsForUid(uid, &deviceIds);
+        // If the UID is associated with multiple virtual devices, use the default device's
+        // sensors as we cannot disambiguate here. This effectively means that the app has
+        // activities on different devices at the same time, so it must handle the device
+        // awareness by itself.
+        if (deviceIds.size() == 1) {
+            const int deviceId = deviceIds.at(0);
+            int devicePolicy = IVirtualDeviceManagerNative::DEVICE_POLICY_DEFAULT;
+            vdm->getDevicePolicy(deviceId,
+                                 IVirtualDeviceManagerNative::POLICY_TYPE_SENSORS,
+                                 &devicePolicy);
+            if (devicePolicy == IVirtualDeviceManagerNative::DEVICE_POLICY_CUSTOM) {
+                return deviceId;
+            }
+        }
+    } else {
+        ALOGW("Cannot get virtualdevice_native service");
+    }
+    return DEVICE_ID_DEFAULT;
+}
+
+}  // namespace
+
 Mutex SensorManager::sLock;
 std::map<String16, SensorManager*> SensorManager::sPackageInstances;
 
@@ -53,6 +92,7 @@
         sensorManager = iterator->second;
     } else {
         String16 opPackageName = packageName;
+        const uid_t uid = IPCThreadState::self()->getCallingUid();
 
         // It is possible that the calling code has no access to the package name.
         // In this case we will get the packages for the calling UID and pick the
@@ -63,7 +103,6 @@
         if (opPackageName.size() <= 0) {
             sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
             if (binder != nullptr) {
-                const uid_t uid = IPCThreadState::self()->getCallingUid();
                 Vector<String16> packages;
                 interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
                 if (!packages.isEmpty()) {
@@ -76,7 +115,10 @@
             }
         }
 
-        sensorManager = new SensorManager(opPackageName);
+        // Check if the calling UID is observed on a virtual device. If so, provide that device's
+        // sensors by default instead of the default device's sensors.
+        const int deviceId = getDeviceIdForUid(uid);
+        sensorManager = new SensorManager(opPackageName, deviceId);
 
         // If we had no package name, we looked it up from the UID and the sensor
         // manager instance we created should also be mapped to the empty package
@@ -102,8 +144,9 @@
     }
 }
 
-SensorManager::SensorManager(const String16& opPackageName)
-    : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) {
+SensorManager::SensorManager(const String16& opPackageName, int deviceId)
+    : mSensorList(nullptr), mOpPackageName(opPackageName), mDeviceId(deviceId),
+        mDirectConnectionHandle(1) {
     Mutex::Autolock _l(mLock);
     assertStateLocked();
 }
@@ -174,7 +217,12 @@
         mDeathObserver = new DeathObserver(*const_cast<SensorManager *>(this));
         IInterface::asBinder(mSensorServer)->linkToDeath(mDeathObserver);
 
-        mSensors = mSensorServer->getSensorList(mOpPackageName);
+        if (mDeviceId == DEVICE_ID_DEFAULT) {
+            mSensors = mSensorServer->getSensorList(mOpPackageName);
+        } else {
+            mSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, mDeviceId);
+        }
+
         size_t count = mSensors.size();
         // If count is 0, mSensorList will be non-null. This is old
         // existing behavior and callers expect this.
@@ -310,6 +358,22 @@
     return false;
 }
 
+bool SensorManager::isReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    if (assertStateLocked() == NO_ERROR) {
+        return mSensorServer->isReplayDataInjectionEnabled();
+    }
+    return false;
+}
+
+bool SensorManager::isHalBypassReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    if (assertStateLocked() == NO_ERROR) {
+        return mSensorServer->isHalBypassReplayDataInjectionEnabled();
+    }
+    return false;
+}
+
 int SensorManager::createDirectChannel(
         size_t size, int channelType, const native_handle_t *resourceHandle) {
     static constexpr int DEFAULT_DEVICE_ID = 0;
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index 5815728..00bc1d6 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -48,6 +48,8 @@
     virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
              int mode, const String16& opPackageName, const String16& attributionTag) = 0;
     virtual int32_t isDataInjectionEnabled() = 0;
+    virtual int32_t isReplayDataInjectionEnabled() = 0;
+    virtual int32_t isHalBypassReplayDataInjectionEnabled() = 0;
 
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             int deviceId, uint32_t size, int32_t type, int32_t format,
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index bb44cb8..e67fac7 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -65,6 +65,8 @@
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
+    bool isReplayDataInjectionEnabled();
+    bool isHalBypassReplayDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
     int createDirectChannel(
         int deviceId, size_t size, int channelType, const native_handle_t *channelData);
@@ -77,7 +79,7 @@
     void sensorManagerDied();
     static status_t waitForSensorService(sp<ISensorServer> *server);
 
-    explicit SensorManager(const String16& opPackageName);
+    explicit SensorManager(const String16& opPackageName, int deviceId);
     status_t assertStateLocked();
 
 private:
@@ -92,6 +94,7 @@
     Vector<Sensor> mDynamicSensors;
     sp<IBinder::DeathRecipient> mDeathObserver;
     const String16 mOpPackageName;
+    const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
     int32_t mDirectConnectionHandle;
 };
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index 2be98e7..57c74ee 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -32,27 +32,12 @@
 sp<hardware::ISensorPrivacyManager> SensorPrivacyManager::getService()
 {
     std::lock_guard<Mutex> scoped_lock(mLock);
-    int64_t startTime = 0;
     sp<hardware::ISensorPrivacyManager> service = mService;
-    while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
-        sp<IBinder> binder = defaultServiceManager()->checkService(String16("sensor_privacy"));
-        if (binder == nullptr) {
-            // Wait for the sensor privacy service to come back...
-            if (startTime == 0) {
-                startTime = uptimeMillis();
-                ALOGI("Waiting for sensor privacy service");
-            } else if ((uptimeMillis() - startTime) > 1000000) {
-                ALOGW("Waiting too long for sensor privacy service, giving up");
-                service = nullptr;
-                break;
-            }
-            usleep(25000);
-        } else {
-            service = interface_cast<hardware::ISensorPrivacyManager>(binder);
-            mService = service;
-        }
+    if (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+      sp<IBinder> binder = defaultServiceManager()->waitForService(String16("sensor_privacy"));
+      mService = interface_cast<hardware::ISensorPrivacyManager>(binder);
     }
-    return service;
+    return mService;
 }
 
 bool SensorPrivacyManager::supportsSensorToggle(int toggleType, int sensor) {
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index c3b2d3d..37ebfc4 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -31,10 +31,15 @@
 using namespace aidl::android::hardware::graphics::common;
 using namespace ::android::hardware::graphics::mapper;
 
+using ADataspace = aidl::android::hardware::graphics::common::Dataspace;
+using APixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+
 namespace android {
 
 static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default");
 static const auto kIAllocatorMinimumVersion = 2;
+constexpr const char* kStandardMetadataName =
+        "android.hardware.graphics.common.StandardMetadataType";
 
 // TODO(b/72323293, b/72703005): Remove these invalid bits from callers
 static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
@@ -284,17 +289,205 @@
     return mMapper != nullptr && mMapper->version >= AIMAPPER_VERSION_5;
 }
 
+static bool isStandardMetadata(AIMapper_MetadataType metadataType) {
+    return strcmp(kStandardMetadataName, metadataType.name) == 0;
+}
+
+struct DumpBufferResult {
+    uint64_t bufferId;
+    std::string name;
+    uint64_t width;
+    uint64_t height;
+    uint64_t layerCount;
+    APixelFormat pixelFormatRequested;
+    uint32_t pixelFormatFourCC;
+    uint64_t pixelFormatModifier;
+    BufferUsage usage;
+    ADataspace dataspace;
+    uint64_t allocationSize;
+    uint64_t protectedContent;
+    ExtendableType compression;
+    ExtendableType interlaced;
+    ExtendableType chromaSiting;
+    std::vector<ui::PlaneLayout> planeLayouts;
+};
+
+#define DECODE_TO(name, output)                                                                 \
+    case StandardMetadataType::name:                                                            \
+        output = StandardMetadata<StandardMetadataType::name>::value ::decode(value, valueSize) \
+                         .value();                                                              \
+        break
+
+static void dumpBufferCommon(DumpBufferResult* outResult, AIMapper_MetadataType metadataType,
+                             const void* value, size_t valueSize) {
+    if (!isStandardMetadata(metadataType)) {
+        return;
+    }
+    StandardMetadataType type = (StandardMetadataType)metadataType.value;
+    switch (type) {
+        DECODE_TO(BUFFER_ID, outResult->bufferId);
+        DECODE_TO(NAME, outResult->name);
+        DECODE_TO(WIDTH, outResult->width);
+        DECODE_TO(HEIGHT, outResult->height);
+        DECODE_TO(LAYER_COUNT, outResult->layerCount);
+        DECODE_TO(PIXEL_FORMAT_REQUESTED, outResult->pixelFormatRequested);
+        DECODE_TO(PIXEL_FORMAT_FOURCC, outResult->pixelFormatFourCC);
+        DECODE_TO(PIXEL_FORMAT_MODIFIER, outResult->pixelFormatModifier);
+        DECODE_TO(USAGE, outResult->usage);
+        DECODE_TO(DATASPACE, outResult->dataspace);
+        DECODE_TO(ALLOCATION_SIZE, outResult->allocationSize);
+        DECODE_TO(PROTECTED_CONTENT, outResult->protectedContent);
+        DECODE_TO(COMPRESSION, outResult->compression);
+        DECODE_TO(INTERLACED, outResult->interlaced);
+        DECODE_TO(CHROMA_SITING, outResult->chromaSiting);
+        DECODE_TO(PLANE_LAYOUTS, outResult->planeLayouts);
+        default:
+            break;
+    }
+}
+
+#undef DECODE_TO
+
+template <typename EnumT, typename = std::enable_if_t<std::is_enum<EnumT>{}>>
+constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept {
+    return static_cast<std::underlying_type_t<EnumT>>(e);
+}
+
+static void writeDumpToStream(const DumpBufferResult& bufferDump, std::ostream& outDump,
+                              bool less) {
+    double allocationSizeKiB = static_cast<double>(bufferDump.allocationSize) / 1024;
+
+    outDump << "+ name:" << bufferDump.name << ", id:" << bufferDump.bufferId
+            << ", size:" << std::fixed << allocationSizeKiB << "KiB, w/h:" << bufferDump.width
+            << "x" << bufferDump.height << ", usage: 0x" << std::hex
+            << to_underlying(bufferDump.usage) << std::dec
+            << ", req fmt:" << to_underlying(bufferDump.pixelFormatRequested)
+            << ", fourcc/mod:" << bufferDump.pixelFormatFourCC << "/"
+            << bufferDump.pixelFormatModifier << ", dataspace: 0x" << std::hex
+            << to_underlying(bufferDump.dataspace) << std::dec << ", compressed: ";
+
+    if (less) {
+        bool isCompressed = !gralloc4::isStandardCompression(bufferDump.compression) ||
+                (gralloc4::getStandardCompressionValue(bufferDump.compression) !=
+                 ui::Compression::NONE);
+        outDump << std::boolalpha << isCompressed << "\n";
+    } else {
+        outDump << gralloc4::getCompressionName(bufferDump.compression) << "\n";
+    }
+
+    if (!less) {
+        bool firstPlane = true;
+        for (const auto& planeLayout : bufferDump.planeLayouts) {
+            if (firstPlane) {
+                firstPlane = false;
+                outDump << "\tplanes: ";
+            } else {
+                outDump << "\t        ";
+            }
+
+            for (size_t i = 0; i < planeLayout.components.size(); i++) {
+                const auto& planeLayoutComponent = planeLayout.components[i];
+                outDump << gralloc4::getPlaneLayoutComponentTypeName(planeLayoutComponent.type);
+                if (i < planeLayout.components.size() - 1) {
+                    outDump << "/";
+                } else {
+                    outDump << ":\t";
+                }
+            }
+            outDump << " w/h:" << planeLayout.widthInSamples << "x" << planeLayout.heightInSamples
+                    << ", stride:" << planeLayout.strideInBytes
+                    << " bytes, size:" << planeLayout.totalSizeInBytes;
+            outDump << ", inc:" << planeLayout.sampleIncrementInBits
+                    << " bits, subsampling w/h:" << planeLayout.horizontalSubsampling << "x"
+                    << planeLayout.verticalSubsampling;
+            outDump << "\n";
+        }
+
+        outDump << "\tlayer cnt: " << bufferDump.layerCount
+                << ", protected content: " << bufferDump.protectedContent
+                << ", interlaced: " << gralloc4::getInterlacedName(bufferDump.interlaced)
+                << ", chroma siting:" << gralloc4::getChromaSitingName(bufferDump.chromaSiting)
+                << "\n";
+    }
+}
+
 std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const {
-    // TODO(b/261858392): Implement
-    (void)bufferHandle;
-    (void)less;
-    return {};
+    DumpBufferResult bufferInfo;
+    AIMapper_DumpBufferCallback dumpBuffer = [](void* contextPtr,
+                                                AIMapper_MetadataType metadataType,
+                                                const void* _Nonnull value, size_t valueSize) {
+        DumpBufferResult* context = reinterpret_cast<DumpBufferResult*>(contextPtr);
+        dumpBufferCommon(context, metadataType, value, valueSize);
+    };
+    AIMapper_Error error = mMapper->v5.dumpBuffer(bufferHandle, dumpBuffer, &bufferInfo);
+    if (error != AIMAPPER_ERROR_NONE) {
+        ALOGE("Error dumping buffer: %d", error);
+        return std::string{};
+    }
+    std::ostringstream stream;
+    stream.precision(2);
+    writeDumpToStream(bufferInfo, stream, less);
+    return stream.str();
 }
 
 std::string Gralloc5Mapper::dumpBuffers(bool less) const {
-    // TODO(b/261858392): Implement
-    (void)less;
-    return {};
+    class DumpAllBuffersContext {
+    private:
+        bool mHasPending = false;
+        DumpBufferResult mPending;
+        std::vector<DumpBufferResult> mResults;
+
+    public:
+        DumpAllBuffersContext() { mResults.reserve(10); }
+
+        void commit() {
+            if (mHasPending) {
+                mResults.push_back(mPending);
+                mHasPending = false;
+            }
+        }
+
+        DumpBufferResult* write() {
+            mHasPending = true;
+            return &mPending;
+        }
+
+        const std::vector<DumpBufferResult>& results() {
+            commit();
+            return mResults;
+        }
+    } context;
+
+    AIMapper_BeginDumpBufferCallback beginCallback = [](void* contextPtr) {
+        DumpAllBuffersContext* context = reinterpret_cast<DumpAllBuffersContext*>(contextPtr);
+        context->commit();
+    };
+
+    AIMapper_DumpBufferCallback dumpBuffer = [](void* contextPtr,
+                                                AIMapper_MetadataType metadataType,
+                                                const void* _Nonnull value, size_t valueSize) {
+        DumpAllBuffersContext* context = reinterpret_cast<DumpAllBuffersContext*>(contextPtr);
+        dumpBufferCommon(context->write(), metadataType, value, valueSize);
+    };
+
+    AIMapper_Error error = mMapper->v5.dumpAllBuffers(beginCallback, dumpBuffer, &context);
+    if (error != AIMAPPER_ERROR_NONE) {
+        ALOGE("Error dumping buffers: %d", error);
+        return std::string{};
+    }
+    uint64_t totalAllocationSize = 0;
+    std::ostringstream stream;
+    stream.precision(2);
+    stream << "Imported gralloc buffers:\n";
+
+    for (const auto& bufferDump : context.results()) {
+        writeDumpToStream(bufferDump, stream, less);
+        totalAllocationSize += bufferDump.allocationSize;
+    }
+
+    double totalAllocationSizeKiB = static_cast<double>(totalAllocationSize) / 1024;
+    stream << "Total imported by gralloc: " << totalAllocationSizeKiB << "KiB\n";
+    return stream.str();
 }
 
 status_t Gralloc5Mapper::importBuffer(const native_handle_t *rawHandle,
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index c0abec2..eb0bd4e 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -89,14 +89,14 @@
     uint64_t total = 0;
     result.append("GraphicBufferAllocator buffers:\n");
     const size_t count = list.size();
-    StringAppendF(&result, "%10s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
+    StringAppendF(&result, "%14s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
                   "W (Stride) x H", "Layers", "Format", "Usage", "Requestor");
     for (size_t i = 0; i < count; i++) {
         const alloc_rec_t& rec(list.valueAt(i));
         std::string sizeStr = (rec.size)
                 ? base::StringPrintf("%7.2f KiB", static_cast<double>(rec.size) / 1024.0)
                 : "unknown";
-        StringAppendF(&result, "%10p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
+        StringAppendF(&result, "%14p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
                       list.keyAt(i), sizeStr.c_str(), rec.width, rec.stride, rec.height,
                       rec.layerCount, rec.format, rec.usage, rec.requestorName.c_str());
         total += rec.size;
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index 65a8769..a469c78 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -37,7 +37,9 @@
     float yDpi = 0;
     std::vector<ui::Hdr> supportedHdrTypes;
 
-    float refreshRate = 0;
+    // Some modes have peak refresh rate lower than the panel vsync rate.
+    float refreshRate = 0.f;
+    float vsyncRate = 0.f;
     nsecs_t appVsyncOffset = 0;
     nsecs_t sfVsyncOffset = 0;
     nsecs_t presentationDeadline = 0;
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index dbe475b..f859848 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -36,6 +36,15 @@
 
 #include <hardware/gralloc.h>
 
+#if defined(__ANDROID_APEX__) || defined(__ANDROID_VNDK__)
+// TODO: Provide alternatives that aren't broken
+#define AHB_CONVERSION                                                                          \
+    [[deprecated("WARNING: VNDK casts beteween GraphicBuffer & AHardwareBuffer are UNSAFE and " \
+                 "will be removed in the future")]]
+#else
+#define AHB_CONVERSION
+#endif
+
 namespace android {
 
 class GraphicBufferMapper;
@@ -80,10 +89,10 @@
 
     static sp<GraphicBuffer> from(ANativeWindowBuffer *);
 
-    static GraphicBuffer* fromAHardwareBuffer(AHardwareBuffer*);
-    static GraphicBuffer const* fromAHardwareBuffer(AHardwareBuffer const*);
-    AHardwareBuffer* toAHardwareBuffer();
-    AHardwareBuffer const* toAHardwareBuffer() const;
+    AHB_CONVERSION static GraphicBuffer* fromAHardwareBuffer(AHardwareBuffer*);
+    AHB_CONVERSION static GraphicBuffer const* fromAHardwareBuffer(AHardwareBuffer const*);
+    AHB_CONVERSION AHardwareBuffer* toAHardwareBuffer();
+    AHB_CONVERSION AHardwareBuffer const* toAHardwareBuffer() const;
 
     // Create a GraphicBuffer to be unflatten'ed into or be reallocated.
     GraphicBuffer();
diff --git a/libs/ui/include/ui/ShadowSettings.h b/libs/ui/include/ui/ShadowSettings.h
new file mode 100644
index 0000000..c0b83b8
--- /dev/null
+++ b/libs/ui/include/ui/ShadowSettings.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <math/vec4.h>
+#include "FloatRect.h"
+
+namespace android {
+
+/*
+ * Contains the configuration for the shadows drawn by single layer. Shadow follows
+ * material design guidelines.
+ */
+struct ShadowSettings {
+    // Boundaries of the shadow.
+    FloatRect boundaries = FloatRect();
+
+    // Color to the ambient shadow. The alpha is premultiplied.
+    vec4 ambientColor = vec4();
+
+    // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
+    // depends on the light position.
+    vec4 spotColor = vec4();
+
+    // Position of the light source used to cast the spot shadow.
+    vec3 lightPos = vec3();
+
+    // Radius of the spot light source. Smaller radius will have sharper edges,
+    // larger radius will have softer shadows
+    float lightRadius = 0.f;
+
+    // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
+    float length = 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;
+};
+
+static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+    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.casterIsTranslucent == rhs.casterIsTranslucent;
+}
+
+static inline bool operator!=(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+    return !(operator==(lhs, rhs));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
index ad1d57a..f1f4035 100644
--- a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
+++ b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
@@ -54,7 +54,7 @@
     std::cout << "input buffer size " << jpegImgR.length << std::endl;
     std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
 #endif
-    size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8);
+    size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
     jpegr_uncompressed_struct decodedJpegR;
     auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
     decodedJpegR.data = decodedRaw.get();
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
index bbe58e0..2d59e8b 100644
--- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
@@ -23,22 +23,12 @@
 
 // User include files
 #include "ultrahdr/gainmapmath.h"
+#include "ultrahdr/jpegdecoderhelper.h"
 #include "ultrahdr/jpegencoderhelper.h"
 #include "utils/Log.h"
 
 using namespace android::ultrahdr;
 
-// constants
-const int kMinWidth = 8;
-const int kMaxWidth = 7680;
-
-const int kMinHeight = 8;
-const int kMaxHeight = 4320;
-
-const int kScaleFactor = 4;
-
-const int kJpegBlock = 16;
-
 // Color gamuts for image data, sync with ultrahdr.h
 const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
 const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
@@ -60,7 +50,7 @@
     UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
     void process();
     void fillP010Buffer(uint16_t* data, int width, int height, int stride);
-    void fill420Buffer(uint8_t* data, int size);
+    void fill420Buffer(uint8_t* data, int width, int height, int stride);
 
 private:
     FuzzedDataProvider mFdp;
@@ -70,11 +60,12 @@
     uint16_t* tmp = data;
     std::vector<uint16_t> buffer(16);
     for (int i = 0; i < buffer.size(); i++) {
-        buffer[i] = mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1);
+        buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6;
     }
     for (int j = 0; j < height; j++) {
         for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i)));
+            memcpy(tmp + i, buffer.data(),
+                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
             std::shuffle(buffer.begin(), buffer.end(),
                          std::default_random_engine(std::random_device{}()));
         }
@@ -82,13 +73,18 @@
     }
 }
 
-void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int size) {
+void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) {
+    uint8_t* tmp = data;
     std::vector<uint8_t> buffer(16);
     mFdp.ConsumeData(buffer.data(), buffer.size());
-    for (int i = 0; i < size; i += buffer.size()) {
-        memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i)));
-        std::shuffle(buffer.begin(), buffer.end(),
-                     std::default_random_engine(std::random_device{}()));
+    for (int j = 0; j < height; j++) {
+        for (int i = 0; i < width; i += buffer.size()) {
+            memcpy(tmp + i, buffer.data(),
+                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
+            std::shuffle(buffer.begin(), buffer.end(),
+                         std::default_random_engine(std::random_device{}()));
+        }
+        tmp += stride;
     }
 }
 
@@ -129,9 +125,10 @@
         int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
         height = (height >> 1) << 1;
 
-        std::unique_ptr<uint16_t[]> bufferY = nullptr;
-        std::unique_ptr<uint16_t[]> bufferUV = nullptr;
-        std::unique_ptr<uint8_t[]> yuv420ImgRaw = nullptr;
+        std::unique_ptr<uint16_t[]> bufferYHdr = nullptr;
+        std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr;
+        std::unique_ptr<uint8_t[]> bufferYSdr = nullptr;
+        std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr;
         std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
         if (muxSwitch != 4) {
             // init p010 image
@@ -145,30 +142,27 @@
             int bppP010 = 2;
             if (isUVContiguous) {
                 size_t p010Size = yStride * height * 3 / 2;
-                bufferY = std::make_unique<uint16_t[]>(p010Size);
-                p010Img.data = bufferY.get();
+                bufferYHdr = std::make_unique<uint16_t[]>(p010Size);
+                p010Img.data = bufferYHdr.get();
                 p010Img.chroma_data = nullptr;
                 p010Img.chroma_stride = 0;
-                fillP010Buffer(bufferY.get(), width, height, yStride);
-                fillP010Buffer(bufferY.get() + yStride * height, width, height / 2, yStride);
+                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
+                fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride);
             } else {
                 int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
                 size_t p010YSize = yStride * height;
-                bufferY = std::make_unique<uint16_t[]>(p010YSize);
-                p010Img.data = bufferY.get();
-                fillP010Buffer(bufferY.get(), width, height, yStride);
+                bufferYHdr = std::make_unique<uint16_t[]>(p010YSize);
+                p010Img.data = bufferYHdr.get();
+                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
                 size_t p010UVSize = uvStride * p010Img.height / 2;
-                bufferUV = std::make_unique<uint16_t[]>(p010UVSize);
-                p010Img.chroma_data = bufferUV.get();
+                bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize);
+                p010Img.chroma_data = bufferUVHdr.get();
                 p010Img.chroma_stride = uvStride;
-                fillP010Buffer(bufferUV.get(), width, height / 2, uvStride);
+                fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride);
             }
         } else {
-            int map_width = width / kScaleFactor;
-            int map_height = height / kScaleFactor;
-            map_width = static_cast<size_t>(floor((map_width + kJpegBlock - 1) / kJpegBlock)) *
-                    kJpegBlock;
-            map_height = ((map_height + 1) >> 1) << 1;
+            size_t map_width = width / kMapDimensionScaleFactor;
+            size_t map_height = height / kMapDimensionScaleFactor;
             // init 400 image
             grayImg.width = map_width;
             grayImg.height = map_height;
@@ -177,7 +171,7 @@
             const size_t graySize = map_width * map_height;
             grayImgRaw = std::make_unique<uint8_t[]>(graySize);
             grayImg.data = grayImgRaw.get();
-            fill420Buffer(grayImgRaw.get(), graySize);
+            fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width);
             grayImg.chroma_data = nullptr;
             grayImg.luma_stride = 0;
             grayImg.chroma_stride = 0;
@@ -185,17 +179,38 @@
 
         if (muxSwitch > 0) {
             // init 420 image
+            bool isUVContiguous = mFdp.ConsumeBool();
+            bool hasYStride = mFdp.ConsumeBool();
+            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
             yuv420Img.width = width;
             yuv420Img.height = height;
             yuv420Img.colorGamut = yuv420Cg;
-
-            const size_t yuv420Size = (yuv420Img.width * yuv420Img.height * 3) / 2;
-            yuv420ImgRaw = std::make_unique<uint8_t[]>(yuv420Size);
-            yuv420Img.data = yuv420ImgRaw.get();
-            fill420Buffer(yuv420ImgRaw.get(), yuv420Size);
-            yuv420Img.chroma_data = nullptr;
-            yuv420Img.luma_stride = 0;
-            yuv420Img.chroma_stride = 0;
+            yuv420Img.luma_stride = hasYStride ? yStride : 0;
+            if (isUVContiguous) {
+                size_t yuv420Size = yStride * height * 3 / 2;
+                bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size);
+                yuv420Img.data = bufferYSdr.get();
+                yuv420Img.chroma_data = nullptr;
+                yuv420Img.chroma_stride = 0;
+                fill420Buffer(bufferYSdr.get(), width, height, yStride);
+                fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2,
+                              yStride / 2);
+                fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2,
+                              yStride / 2);
+            } else {
+                int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128);
+                size_t yuv420YSize = yStride * height;
+                bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize);
+                yuv420Img.data = bufferYSdr.get();
+                fill420Buffer(bufferYSdr.get(), width, height, yStride);
+                size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2;
+                bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize);
+                yuv420Img.chroma_data = bufferUVSdr.get();
+                yuv420Img.chroma_stride = uvStride;
+                fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride);
+                fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2,
+                              uvStride);
+            }
         }
 
         // dest
@@ -212,6 +227,8 @@
         std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
         std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
         std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
+        std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl;
+        std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl;
         std::cout << "quality factor " << quality << std::endl;
 #endif
 
@@ -226,8 +243,19 @@
         } else {
             // compressed img
             JpegEncoderHelper encoder;
-            if (encoder.compressImage(yuv420Img.data, yuv420Img.width, yuv420Img.height, quality,
-                                      nullptr, 0)) {
+            struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img;
+            if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width;
+            if (!yuv420ImgCopy.chroma_data) {
+                uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data);
+                yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height;
+                yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1;
+            }
+
+            if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data),
+                                      reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data),
+                                      yuv420ImgCopy.width, yuv420ImgCopy.height,
+                                      yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride,
+                                      quality, nullptr, 0)) {
                 jpegImg.length = encoder.getCompressedImageSize();
                 jpegImg.maxLength = jpegImg.length;
                 jpegImg.data = encoder.getCompressedImagePtr();
@@ -242,14 +270,15 @@
                 } else if (muxSwitch == 4) { // api 4
                     jpegImgR.length = 0;
                     JpegEncoderHelper gainMapEncoder;
-                    if (gainMapEncoder.compressImage(grayImg.data, grayImg.width, grayImg.height,
-                                                     quality, nullptr, 0, true)) {
+                    if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data),
+                                                     nullptr, grayImg.width, grayImg.height,
+                                                     grayImg.width, 0, quality, nullptr, 0)) {
                         jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
                         jpegGainMap.maxLength = jpegImg.length;
                         jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
                         jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
                         ultrahdr_metadata_struct metadata;
-                        metadata.version = "1.0";
+                        metadata.version = kJpegrVersion;
                         if (tf == ULTRAHDR_TF_HLG) {
                             metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
                         } else if (tf == ULTRAHDR_TF_PQ) {
@@ -274,7 +303,8 @@
             jpegr_info_struct info{0, 0, &iccData, &exifData};
             status = jpegHdr.getJPEGRInfo(&jpegImgR, &info);
             if (status == android::OK) {
-                size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8);
+                size_t outSize =
+                        info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
                 jpegr_uncompressed_struct decodedJpegR;
                 auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
                 decodedJpegR.data = decodedRaw.get();
diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
index ee15363..ae9c4ca 100644
--- a/libs/ultrahdr/gainmapmath.cpp
+++ b/libs/ultrahdr/gainmapmath.cpp
@@ -168,7 +168,7 @@
 
 // See IEC 61966-2-1, Equations F.5 and F.6.
 float srgbInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * kSrgbInvOETFNumEntries);
+  uint32_t value = static_cast<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
   //TODO() : Remove once conversion modules have appropriate clamping in place
   value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
   return kSrgbInvOETF[value];
@@ -288,7 +288,7 @@
 }
 
 float hlgOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * kHlgOETFNumEntries);
+  uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
   //TODO() : Remove once conversion modules have appropriate clamping in place
   value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
 
@@ -315,7 +315,7 @@
 }
 
 float hlgInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * kHlgInvOETFNumEntries);
+  uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
   //TODO() : Remove once conversion modules have appropriate clamping in place
   value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
 
@@ -344,7 +344,7 @@
 }
 
 float pqOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * kPqOETFNumEntries);
+  uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
   //TODO() : Remove once conversion modules have appropriate clamping in place
   value = CLIP3(value, 0, kPqOETFNumEntries - 1);
 
@@ -376,7 +376,7 @@
 }
 
 float pqInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * kPqInvOETFNumEntries);
+  uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
   //TODO() : Remove once conversion modules have appropriate clamping in place
   value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
 
@@ -531,29 +531,29 @@
 
   Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
 
-  size_t pixel_y1_idx =  x_chroma * 2      +  y_chroma * 2      * image->width;
-  size_t pixel_y2_idx = (x_chroma * 2 + 1) +  y_chroma * 2      * image->width;
-  size_t pixel_y3_idx =  x_chroma * 2      + (y_chroma * 2 + 1) * image->width;
-  size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->width;
+  size_t pixel_y1_idx =  x_chroma * 2      +  y_chroma * 2      * image->luma_stride;
+  size_t pixel_y2_idx = (x_chroma * 2 + 1) +  y_chroma * 2      * image->luma_stride;
+  size_t pixel_y3_idx =  x_chroma * 2      + (y_chroma * 2 + 1) * image->luma_stride;
+  size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride;
 
   uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
   uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
   uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
   uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
 
-  size_t pixel_count = image->width * image->height;
-  size_t pixel_uv_idx = x_chroma + y_chroma * (image->width / 2);
+  size_t pixel_count = image->chroma_stride * image->height / 2;
+  size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride);
 
-  uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
-  uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+  uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_uv_idx];
+  uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_count + pixel_uv_idx];
 
-  y1_uint = static_cast<uint8_t>(floor(yuv1.y * 255.0f + 0.5f));
-  y2_uint = static_cast<uint8_t>(floor(yuv2.y * 255.0f + 0.5f));
-  y3_uint = static_cast<uint8_t>(floor(yuv3.y * 255.0f + 0.5f));
-  y4_uint = static_cast<uint8_t>(floor(yuv4.y * 255.0f + 0.5f));
+  y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
+  y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
+  y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
+  y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
 
-  u_uint = static_cast<uint8_t>(floor(new_uv.u * 255.0f + 128.0f + 0.5f));
-  v_uint = static_cast<uint8_t>(floor(new_uv.v * 255.0f + 128.0f + 0.5f));
+  u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
+  v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -598,14 +598,18 @@
 }
 
 Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  size_t pixel_count = image->width * image->height;
+  uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->data);
+  size_t luma_stride = image->luma_stride;
+  uint8_t* chroma_data = reinterpret_cast<uint8_t*>(image->chroma_data);
+  size_t chroma_stride = image->chroma_stride;
 
-  size_t pixel_y_idx = x + y * image->width;
-  size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+  size_t offset_cr = chroma_stride * (image->height / 2);
+  size_t pixel_y_idx = x + y * luma_stride;
+  size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride;
 
-  uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y_idx];
-  uint8_t u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
-  uint8_t v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+  uint8_t y_uint = luma_data[pixel_y_idx];
+  uint8_t u_uint = chroma_data[pixel_chroma_idx];
+  uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx];
 
   // 128 bias for UV given we are using jpeglib; see:
   // https://github.com/kornelski/libjpeg/blob/master/structure.doc
@@ -615,20 +619,10 @@
 }
 
 Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  size_t luma_stride = image->luma_stride;
-  size_t chroma_stride = image->chroma_stride;
   uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
+  size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride;
   uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
-
-  if (luma_stride == 0) {
-    luma_stride = image->width;
-  }
-  if (chroma_stride == 0) {
-    chroma_stride = luma_stride;
-  }
-  if (chroma_data == nullptr) {
-    chroma_data = &reinterpret_cast<uint16_t*>(image->data)[luma_stride * image->height];
-  }
+  size_t chroma_stride = image->chroma_stride;
 
   size_t pixel_y_idx = y * luma_stride + x;
   size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
index 1ab3c7c..e41b645 100644
--- a/libs/ultrahdr/icc.cpp
+++ b/libs/ultrahdr/icc.cpp
@@ -19,7 +19,6 @@
 #endif
 
 #include <ultrahdr/icc.h>
-#include <ultrahdr/gainmapmath.h>
 #include <vector>
 #include <utils/Log.h>
 
@@ -218,8 +217,8 @@
     total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
     dataStruct->write32(Endian_SwapBE32(kTAG_CurveType));     // Type
-    dataStruct->write32(0);                                     // Reserved
-    dataStruct->write32(Endian_SwapBE32(table_entries));  // Value count
+    dataStruct->write32(0);                                   // Reserved
+    dataStruct->write32(Endian_SwapBE32(table_entries));      // Value count
     for (size_t i = 0; i < table_entries; ++i) {
         uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
         dataStruct->write16(value);
@@ -227,14 +226,30 @@
     return dataStruct;
 }
 
-sp<DataStruct> IccHelper::write_trc_tag_for_linear() {
-    int total_length = 16;
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-    dataStruct->write32(0);                                      // Reserved
-    dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(1.0)));
+sp<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) {
+    if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f
+            && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) {
+        int total_length = 16;
+        sp<DataStruct> dataStruct = new DataStruct(total_length);
+        dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
+        dataStruct->write32(0);                                    // Reserved
+        dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
+        dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
+        return dataStruct;
+    }
 
+    int total_length = 40;
+    sp<DataStruct> dataStruct = new DataStruct(total_length);
+    dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
+    dataStruct->write32(0);                                    // Reserved
+    dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a)));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b)));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c)));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d)));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e)));
+    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f)));
     return dataStruct;
 }
 
@@ -349,7 +364,7 @@
 
     // The "B" curve is required.
     for (size_t i = 0; i < kNumChannels; ++i) {
-        b_curves_data[i] = write_trc_tag_for_linear();
+        b_curves_data[i] = write_trc_tag(kLinear_TransFun);
     }
 
     // The "A" curve and CLUT are optional.
@@ -362,7 +377,7 @@
 
         a_curves_offset = clut_offset + clut->getLength();
         for (size_t i = 0; i < kNumChannels; ++i) {
-            a_curves_data[i] = write_trc_tag_for_linear();
+            a_curves_data[i] = write_trc_tag(kLinear_TransFun);
         }
     }
 
@@ -460,9 +475,9 @@
             tags.emplace_back(kTAG_bTRC,
                     write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
         } else {
-            tags.emplace_back(kTAG_rTRC, write_trc_tag_for_linear());
-            tags.emplace_back(kTAG_gTRC, write_trc_tag_for_linear());
-            tags.emplace_back(kTAG_bTRC, write_trc_tag_for_linear());
+            tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun));
+            tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun));
+            tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun));
         }
     }
 
diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
index edf152d..9f1238f 100644
--- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h
+++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
@@ -51,6 +51,23 @@
 typedef Color (*ColorTransformFn)(Color);
 typedef float (*ColorCalculationFn)(Color);
 
+// A transfer function mapping encoded values to linear values,
+// represented by this 7-parameter piecewise function:
+//
+//   linear = sign(encoded) *  (c*|encoded| + f)       , 0 <= |encoded| < d
+//          = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
+//
+// (A simple gamma transfer function sets g to gamma and a to 1.)
+typedef struct TransferFunction {
+    float g, a,b,c,d,e,f;
+} TransferFunction;
+
+static constexpr TransferFunction kSRGB_TransFun =
+    { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
+
+static constexpr TransferFunction kLinear_TransFun =
+    { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
 inline Color operator+=(Color& lhs, const Color& rhs) {
   lhs.r += rhs.r;
   lhs.g += rhs.g;
@@ -155,7 +172,7 @@
   }
 
   float getGainFactor(float gain) {
-    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1));
+    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
     //TODO() : Remove once conversion modules have appropriate clamping in place
     idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
     return mGainTable[idx];
@@ -312,7 +329,7 @@
 float hlgOetfLUT(float e);
 Color hlgOetfLUT(Color e);
 
-constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFPrecision = 16;
 constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
 
 /*
@@ -325,7 +342,7 @@
 float hlgInvOetfLUT(float e_gamma);
 Color hlgInvOetfLUT(Color e_gamma);
 
-constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFPrecision = 12;
 constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
 
 /*
@@ -338,7 +355,7 @@
 float pqOetfLUT(float e);
 Color pqOetfLUT(Color e);
 
-constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFPrecision = 16;
 constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
 
 /*
@@ -351,7 +368,7 @@
 float pqInvOetfLUT(float e_gamma);
 Color pqInvOetfLUT(Color e_gamma);
 
-constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFPrecision = 12;
 constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
 
 
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
index 7f047f8..971b267 100644
--- a/libs/ultrahdr/include/ultrahdr/icc.h
+++ b/libs/ultrahdr/include/ultrahdr/icc.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_ULTRAHDR_ICC_H
 #define ANDROID_ULTRAHDR_ICC_H
 
+#include <ultrahdr/gainmapmath.h>
 #include <ultrahdr/jpegr.h>
 #include <ultrahdr/jpegrutils.h>
 #include <utils/RefBase.h>
@@ -222,7 +223,7 @@
                                        const ultrahdr_color_gamut gamut);
     static sp<DataStruct> write_xyz_tag(float x, float y, float z);
     static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
-    static sp<DataStruct> write_trc_tag_for_linear();
+    static sp<DataStruct> write_trc_tag(const TransferFunction& fn);
     static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L);
     static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
                                          uint32_t transfer_characteristics);
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index 8b5499a..b86ce5f4 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -26,6 +26,8 @@
 #include <utils/Errors.h>
 #include <vector>
 
+// constraint on max width and max height is only due to device alloc constraints
+// Can tune these values basing on the target device
 static const int kMaxWidth = 8192;
 static const int kMaxHeight = 8192;
 
@@ -74,15 +76,27 @@
      */
     size_t getXMPSize();
     /*
+     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+     */
+    bool extractEXIF(const void* image, int length);
+    /*
      * Returns the EXIF data from the image.
+     * This method must be called after extractEXIF() or decompressImage().
      */
     void* getEXIFPtr();
     /*
      * Returns the decompressed EXIF buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
+     * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
      */
     size_t getEXIFSize();
     /*
+     * Returns the position offset of EXIF package
+     * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
+     * or -1  if no EXIF exists.
+     * This method must be called after extractEXIF() or decompressImage().
+     */
+    int getEXIFPos() { return mExifPos; }
+    /*
      * Returns the ICC data from the image.
      */
     void* getICCPtr();
@@ -94,9 +108,8 @@
     /*
      * Decompresses metadata of the image. All vectors are owned by the caller.
      */
-    bool getCompressedImageParameters(const void* image, int length,
-                                      size_t* pWidth, size_t* pHeight,
-                                      std::vector<uint8_t>* iccData,
+    bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+                                      size_t* pHeight, std::vector<uint8_t>* iccData,
                                       std::vector<uint8_t>* exifData);
 
 private:
@@ -121,6 +134,9 @@
     // Resolution of the decompressed image.
     size_t mWidth;
     size_t mHeight;
+
+    // Position of EXIF package, default value is -1 which means no EXIF package appears.
+    ssize_t mExifPos = -1;
 };
 } /* namespace android::ultrahdr  */
 
diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
index 2c6778e..9d06415 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
@@ -19,6 +19,7 @@
 
 // We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
 #include <cstdio>
+#include <vector>
 
 extern "C" {
 #include <jerror.h>
@@ -26,10 +27,11 @@
 }
 
 #include <utils/Errors.h>
-#include <vector>
 
 namespace android::ultrahdr {
 
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
+
 /*
  * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
  * This class is not thread-safe.
@@ -46,8 +48,9 @@
      * ICC segment which will be added to the compressed image.
      * Returns false if errors occur during compression.
      */
-    bool compressImage(const void* image, int width, int height, int quality,
-                       const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);
+    bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
+                       int lumaStride, int chromaStride, int quality, const void* iccBuffer,
+                       unsigned int iccSize);
 
     /*
      * Returns the compressed JPEG buffer pointer. This method must be called only after calling
@@ -66,6 +69,7 @@
      * We must pass at least 16 scanlines according to libjpeg documentation.
      */
     static const int kCompressBatchSize = 16;
+
 private:
     // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
     // passed into jpeg library.
@@ -75,15 +79,16 @@
     static void outputErrorMessage(j_common_ptr cinfo);
 
     // Returns false if errors occur.
-    bool encode(const void* inYuv, int width, int height, int jpegQuality,
-                const void* iccBuffer, unsigned int iccSize, bool isSingleChannel);
+    bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
+                int lumaStride, int chromaStride, int quality, const void* iccBuffer,
+                unsigned int iccSize);
     void setJpegDestination(jpeg_compress_struct* cinfo);
     void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
                                bool isSingleChannel);
     // Returns false if errors occur.
-    bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel);
-    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv);
-    bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image);
+    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer,
+                     int lumaStride, int chromaStride);
+    bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride);
 
     // The block size for encoded jpeg image buffer.
     static const int kBlockSize = 16384;
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index f80496a..114c81d 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -17,9 +17,13 @@
 #ifndef ANDROID_ULTRAHDR_JPEGR_H
 #define ANDROID_ULTRAHDR_JPEGR_H
 
-#include "jpegencoderhelper.h"
-#include "jpegrerrorcode.h"
-#include "ultrahdr.h"
+#include <cstdint>
+#include <vector>
+
+#include "ultrahdr/jpegdecoderhelper.h"
+#include "ultrahdr/jpegencoderhelper.h"
+#include "ultrahdr/jpegrerrorcode.h"
+#include "ultrahdr/ultrahdr.h"
 
 #ifndef FLT_MAX
 #define FLT_MAX 0x1.fffffep127f
@@ -27,6 +31,27 @@
 
 namespace android::ultrahdr {
 
+// The current JPEGR version that we encode to
+static const char* const kJpegrVersion = "1.0";
+
+// Map is quarter res / sixteenth size
+static const size_t kMapDimensionScaleFactor = 4;
+
+// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
+// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
+// 1 sample is sufficient. We are using 2 here anyways
+static const int kMinWidth = 2 * kMapDimensionScaleFactor;
+static const int kMinHeight = 2 * kMapDimensionScaleFactor;
+
+// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter
+// JpegEncoderHelper::kCompressBatchSize.
+// The width and height of image under compression is expected to be a multiple of MCU size.
+// If this criteria is not satisfied, padding is done.
+static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
+
+/*
+ * Holds information of jpegr image
+ */
 struct jpegr_info_struct {
     size_t width;
     size_t height;
@@ -49,16 +74,19 @@
 
     // Values below are optional
     // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
-    // following after the luma plane.
-    // Note: currently this feature is only supported for P010 image (HDR input).
+    // after the luma plane.
     void* chroma_data = nullptr;
-    // Strides of Y plane in number of pixels, using 0 to present uninitialized, must be
-    // larger than or equal to luma width.
-    // Note: currently this feature is only supported for P010 image (HDR input).
+    // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If
+    // non-zero this value must be larger than or equal to luma width. If stride is
+    // uninitialized then it is assumed to be equal to luma width.
     int luma_stride = 0;
-    // Strides of UV plane in number of pixels, using 0 to present uninitialized, must be
-    // larger than or equal to chroma width.
-    // Note: currently this feature is only supported for P010 image (HDR input).
+    // Stride of UV plane in number of pixels.
+    // 1. If this handle points to P010 image then this value must be larger than
+    //    or equal to luma width.
+    // 2. If this handle points to 420 image then this value must be larger than
+    //    or equal to (luma width / 2).
+    // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
+    // chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
     int chroma_stride = 0;
 };
 
@@ -102,10 +130,10 @@
      * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
      * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
      * JPEG.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
+     *             represents the maximum available size of the destination buffer, and it must be
      *             set before calling this method. If the encoded JPEGR size exceeds
      *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
      * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
@@ -113,11 +141,8 @@
      * @param exif pointer to the exif metadata.
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest,
-                         int quality,
-                         jr_exif_ptr exif);
+    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest, int quality, jr_exif_ptr exif);
 
     /*
      * Encode API-1
@@ -126,8 +151,8 @@
      * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
      * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
      * resolution. SDR input is assumed to use the sRGB transfer function.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
+     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
      *             represents the maximum available size of the desitination buffer, and it must be
@@ -138,11 +163,8 @@
      * @param exif pointer to the exif metadata.
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jr_uncompressed_ptr uncompressed_yuv_420_image,
-                         ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest,
-                         int quality,
+    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
+                         ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality,
                          jr_exif_ptr exif);
 
     /*
@@ -155,11 +177,11 @@
      * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
      * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
      * transfer function.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     *                                   Note: the SDR image must be the decoded version of the JPEG
-     *                                         input
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
+     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+     *                            Note: the compressed SDR image must be the compressed
+     *                                  yuv420_image_ptr image in JPEG format.
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
      *             represents the maximum available size of the desitination buffer, and it must be
@@ -167,10 +189,8 @@
      *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jr_uncompressed_ptr uncompressed_yuv_420_image,
-                         jr_compressed_ptr compressed_jpeg_image,
-                         ultrahdr_transfer_function hdr_tf,
+    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
+                         jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf,
                          jr_compressed_ptr dest);
 
     /*
@@ -183,8 +203,8 @@
      * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an
      * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
      * resolution. JPEG image is assumed to use the sRGB transfer function.
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
+     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
      *             represents the maximum available size of the desitination buffer, and it must be
@@ -192,10 +212,8 @@
      *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                         jr_compressed_ptr compressed_jpeg_image,
-                         ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest);
+    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr,
+                         ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest);
 
     /*
      * Encode API-4
@@ -203,8 +221,8 @@
      *
      * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC
      * profile if one isn't present in the input JPEG image.
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
-     * @param compressed_gainmap compressed 8-bit JPEG single channel image
+     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+     * @param gainmapjpg_image_ptr gain map image compressed in jpeg format
      * @param metadata metadata to be written in XMP of the primary jpeg
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
      *             represents the maximum available size of the desitination buffer, and it must be
@@ -212,9 +230,8 @@
      *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
-                         jr_compressed_ptr compressed_gainmap,
-                         ultrahdr_metadata_ptr metadata,
+    status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
+                         jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
                          jr_compressed_ptr dest);
 
     /*
@@ -227,8 +244,7 @@
      *
      * This method only supports single gain map metadata values for fields that allow multi-channel
      * metadata values.
-     *
-     * @param compressed_jpegr_image compressed JPEGR image.
+     * @param jpegr_image_ptr compressed JPEGR image.
      * @param dest destination of the uncompressed JPEGR image.
      * @param max_display_boost (optional) the maximum available boost supported by a display,
      *                          the value must be greater than or equal to 1.0.
@@ -248,57 +264,55 @@
                             ----------------------------------------------------------------------
                             |   JPEGR_OUTPUT_HDR_HLG   |            RGBA_1010102 HLG             |
                             ----------------------------------------------------------------------
-     * @param gain_map destination of the decoded gain map. The default value is NULL where
-                           the decoder will do nothing about it. If configured not NULL the decoder
-                           will write the decoded gain_map data into this structure. The format
-                           is defined in {@code jpegr_uncompressed_struct}.
+     * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL
+                                where the decoder will do nothing about it. If configured not NULL
+                                the decoder will write the decoded gain_map data into this
+                                structure. The format is defined in
+                                {@code jpegr_uncompressed_struct}.
      * @param metadata destination of the decoded metadata. The default value is NULL where the
                        decoder will do nothing about it. If configured not NULL the decoder will
                        write metadata into this structure. the format of metadata is defined in
                        {@code ultrahdr_metadata_struct}.
      * @return NO_ERROR if decoding succeeds, error code if error occurs.
      */
-    status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
-                         jr_uncompressed_ptr dest,
-                         float max_display_boost = FLT_MAX,
-                         jr_exif_ptr exif = nullptr,
+    status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
+                         float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr,
                          ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR,
-                         jr_uncompressed_ptr gain_map = nullptr,
+                         jr_uncompressed_ptr gainmap_image_ptr = nullptr,
                          ultrahdr_metadata_ptr metadata = nullptr);
 
     /*
-    * Gets Info from JPEGR file without decoding it.
-    *
-    * This method only supports single gain map metadata values for fields that allow multi-channel
-    * metadata values.
-    *
-    * The output is filled jpegr_info structure
-    * @param compressed_jpegr_image compressed JPEGR image
-    * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
-    *         are owned by the caller
-    * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
-    */
-    status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
-                          jr_info_ptr jpegr_info);
+     * Gets Info from JPEGR file without decoding it.
+     *
+     * This method only supports single gain map metadata values for fields that allow multi-channel
+     * metadata values.
+     *
+     * The output is filled jpegr_info structure
+     * @param jpegr_image_ptr compressed JPEGR image
+     * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info
+     *                            are owned by the caller
+     * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+     */
+    status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr);
+
 protected:
     /*
      * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
      * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
      * must be the same resolution. The SDR input is assumed to use the sRGB transfer function.
      *
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
      * @param hdr_tf transfer function of the HDR image
-     * @param dest gain map; caller responsible for memory of data
-     * @param metadata max_content_boost is filled in
+     * @param metadata everything but "version" is filled in this struct
+     * @param dest location at which gain map image is stored (caller responsible for memory
+                   of data).
      * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                             jr_uncompressed_ptr uncompressed_p010_image,
-                             ultrahdr_transfer_function hdr_tf,
-                             ultrahdr_metadata_ptr metadata,
-                             jr_uncompressed_ptr dest,
+    status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+                             jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest,
                              bool sdr_is_601 = false);
 
     /*
@@ -309,8 +323,8 @@
      * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
      * be a decoded JPEG for the purpose of YUV interpration.
      *
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     * @param uncompressed_gain_map uncompressed gain map
+     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+     * @param gainmap_image_ptr pointer to uncompressed gain map image struct.
      * @param metadata JPEG/R metadata extracted from XMP.
      * @param output_format flag for setting output color format. if set to
      *                      {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
@@ -319,70 +333,67 @@
      * @param dest reconstructed HDR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                          jr_uncompressed_ptr uncompressed_gain_map,
-                          ultrahdr_metadata_ptr metadata,
-                          ultrahdr_output_format output_format,
-                          float max_display_boost,
+    status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+                          jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
+                          ultrahdr_output_format output_format, float max_display_boost,
                           jr_uncompressed_ptr dest);
 
 private:
     /*
      * This method is called in the encoding pipeline. It will encode the gain map.
      *
-     * @param uncompressed_gain_map uncompressed gain map
-     * @param resource to compress gain map
+     * @param gainmap_image_ptr pointer to uncompressed gain map image struct
+     * @param jpeg_enc_obj_ptr helper resource to compress gain map
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
-                             JpegEncoderHelper* jpeg_encoder);
+    status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
+                             JpegEncoderHelper* jpeg_enc_obj_ptr);
 
     /*
-     * This methoud is called to separate primary image and gain map image from JPEGR
+     * This method is called to separate primary image and gain map image from JPEGR
      *
-     * @param compressed_jpegr_image compressed JPEGR image
-     * @param primary_image destination of primary image
-     * @param gain_map destination of compressed gain map
+     * @param jpegr_image_ptr pointer to compressed JPEGR image.
+     * @param primary_jpg_image_ptr destination of primary image
+     * @param gainmap_jpg_image_ptr destination of compressed gain map image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
-    */
-    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
-                                           jr_compressed_ptr primary_image,
-                                           jr_compressed_ptr gain_map);
+     */
+    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
+                                           jr_compressed_ptr primary_jpg_image_ptr,
+                                           jr_compressed_ptr gainmap_jpg_image_ptr);
 
     /*
      * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
      * the compressed gain map and optionally the exif package as inputs, and generate the XMP
      * metadata, and finally append everything in the order of:
      *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
-     * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
-     * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
-     * the input JPEG has EXIF.
      *
-     * @param compressed_jpeg_image compressed 8-bit JPEG image
-     * @param compress_gain_map compressed recover map
-     * @param (nullable) exif EXIF package
-     * @param (nullable) icc ICC package
+     * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
+     * conditions is fulfilled:
+     *  (1) EXIF package is available from outside input. I.e. pExif != nullptr.
+     *  (2) Input JPEG has EXIF.
+     * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
+     *
+     * @param primary_jpg_image_ptr destination of primary image
+     * @param gainmap_jpg_image_ptr destination of compressed gain map image
+     * @param (nullable) pExif EXIF package
+     * @param (nullable) pIcc ICC package
      * @param icc_size length in bytes of ICC package
      * @param metadata JPEG/R metadata to encode in XMP of the jpeg
      * @param dest compressed JPEGR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image,
-                           jr_compressed_ptr compressed_gain_map,
-                           jr_exif_ptr exif,
-                           void* icc, size_t icc_size,
-                           ultrahdr_metadata_ptr metadata,
-                           jr_compressed_ptr dest);
+    status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
+                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
+                           size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
 
     /*
      * This method will tone map a HDR image to an SDR image.
      *
-     * @param src (input) uncompressed P010 image
-     * @param dest (output) tone mapping result as a YUV_420 image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     * @param src pointer to uncompressed HDR image struct. HDR image is expected to be
+     *            in p010 color format
+     * @param dest pointer to store tonemapped SDR image
      */
-    status_t toneMap(jr_uncompressed_ptr src,
-                     jr_uncompressed_ptr dest);
+    status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest);
 
     /*
      * This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
@@ -396,15 +407,15 @@
      * @param dest_encoding output YUV encoding
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t convertYuv(jr_uncompressed_ptr image,
-                        ultrahdr_color_gamut src_encoding,
+    status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
                         ultrahdr_color_gamut dest_encoding);
 
     /*
      * This method will check the validity of the input arguments.
      *
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
+     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
+     *                         be in 420p color format
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
      *             represents the maximum available size of the desitination buffer, and it must be
@@ -412,32 +423,30 @@
      *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
      * @return NO_ERROR if the input args are valid, error code is not valid.
      */
-     status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
-                                     jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                     ultrahdr_transfer_function hdr_tf,
-                                     jr_compressed_ptr dest);
+    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+                                    jr_uncompressed_ptr yuv420_image_ptr,
+                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr);
 
     /*
      * This method will check the validity of the input arguments.
      *
-     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
-     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param p010_image_ptr uncompressed HDR image in P010 color format
+     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
+     *                         be in 420p color format
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
+     *             represents the maximum available size of the destination buffer, and it must be
      *             set before calling this method. If the encoded JPEGR size exceeds
      *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
      * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
      *                the highest quality
      * @return NO_ERROR if the input args are valid, error code is not valid.
      */
-     status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
-                                     jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                     ultrahdr_transfer_function hdr_tf,
-                                     jr_compressed_ptr dest,
-                                     int quality);
+    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+                                    jr_uncompressed_ptr yuv420_image_ptr,
+                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest,
+                                    int quality);
 };
-
 } // namespace android::ultrahdr
 
 #endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
index 17cc971..0252391 100644
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_ULTRAHDR_ULTRAHDR_H
 #define ANDROID_ULTRAHDR_ULTRAHDR_H
 
+#include <string>
+
 namespace android::ultrahdr {
 // Color gamuts for image data
 typedef enum {
@@ -28,6 +30,7 @@
 } ultrahdr_color_gamut;
 
 // Transfer functions for image data
+// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
 typedef enum {
   ULTRAHDR_TF_UNSPECIFIED = -1,
   ULTRAHDR_TF_LINEAR = 0,
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
index fef5444..2e7940c 100644
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -26,18 +26,23 @@
 
 namespace android::ultrahdr {
 
-#define ALIGNM(x, m)  ((((x) + ((m) - 1)) / (m)) * (m))
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
 
-const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF
-const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP
-const uint32_t kAPP2Marker = JPEG_APP0 + 2;  // ICC
+const uint32_t kAPP0Marker = JPEG_APP0;     // JFIF
+const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
+const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
 
-const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
-const std::string kExifIdCode = "Exif";
 constexpr uint32_t kICCMarkerHeaderSize = 14;
 constexpr uint8_t kICCSig[] = {
         'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
 };
+constexpr uint8_t kXmpNameSpace[] = {
+        'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
+        '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
+};
+constexpr uint8_t kExifIdCode[] = {
+        'E', 'x', 'i', 'f', '\0', '\0',
+};
 
 struct jpegr_source_mgr : jpeg_source_mgr {
     jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -76,8 +81,8 @@
 
 static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
 
-jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) :
-        mBufferPtr(ptr), mBufferLength(len) {
+jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len)
+      : mBufferPtr(ptr), mBufferLength(len) {
     init_source = jpegr_init_source;
     fill_input_buffer = jpegr_fill_input_buffer;
     skip_input_data = jpegr_skip_input_data;
@@ -92,25 +97,18 @@
     longjmp(err->setjmp_buffer, 1);
 }
 
-JpegDecoderHelper::JpegDecoderHelper() {
-}
+JpegDecoderHelper::JpegDecoderHelper() {}
 
-JpegDecoderHelper::~JpegDecoderHelper() {
-}
+JpegDecoderHelper::~JpegDecoderHelper() {}
 
 bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
     if (image == nullptr || length <= 0) {
         ALOGE("Image size can not be handled: %d", length);
         return false;
     }
-
     mResultBuffer.clear();
     mXMPBuffer.clear();
-    if (!decode(image, length, decodeToRGBA)) {
-        return false;
-    }
-
-    return true;
+    return decode(image, length, decodeToRGBA);
 }
 
 void* JpegDecoderHelper::getDecompressedImagePtr() {
@@ -153,11 +151,15 @@
     return mHeight;
 }
 
-bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
+// in the image file.
+// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+// two bytes of package length which is stored in marker->original_length, and the real data
+// which is stored in marker->data.
+bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
-    bool status = true;
 
     cinfo.err = jpeg_std_error(&myerr.pub);
     myerr.pub.error_exit = jpegrerror_exit;
@@ -170,11 +172,61 @@
 
     jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
     jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
 
     cinfo.src = &mgr;
     jpeg_read_header(&cinfo, TRUE);
 
+    size_t pos = 2;  // position after SOI
+    for (jpeg_marker_struct* marker = cinfo.marker_list;
+         marker;
+         marker = marker->next) {
+
+        pos += 4;
+        pos += marker->original_length;
+
+        if (marker->marker != kAPP1Marker) {
+            continue;
+        }
+
+        const unsigned int len = marker->data_length;
+
+        if (len > sizeof(kExifIdCode) &&
+            !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+            mEXIFBuffer.resize(len, 0);
+            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+            mExifPos = pos - marker->original_length;
+            break;
+        }
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    return true;
+}
+
+bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+    bool status = true;
+    jpeg_decompress_struct cinfo;
+    jpegrerror_mgr myerr;
+    cinfo.err = jpeg_std_error(&myerr.pub);
+    myerr.pub.error_exit = jpegrerror_exit;
+    if (setjmp(myerr.setjmp_buffer)) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+
+    jpeg_create_decompress(&cinfo);
+
+    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+    cinfo.src = &mgr;
+    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+
     // Save XMP data, EXIF data, and ICC data.
     // Here we only handle the first XMP / EXIF / ICC package.
     // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
@@ -183,30 +235,29 @@
     bool exifAppears = false;
     bool xmpAppears = false;
     bool iccAppears = false;
+    size_t pos = 2;  // position after SOI
     for (jpeg_marker_struct* marker = cinfo.marker_list;
          marker && !(exifAppears && xmpAppears && iccAppears);
          marker = marker->next) {
-
+         pos += 4;
+         pos += marker->original_length;
         if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
             continue;
         }
         const unsigned int len = marker->data_length;
         if (!xmpAppears &&
-            len > kXmpNameSpace.size() &&
-            !strncmp(reinterpret_cast<const char*>(marker->data),
-                     kXmpNameSpace.c_str(),
-                     kXmpNameSpace.size())) {
+            len > sizeof(kXmpNameSpace) &&
+            !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
             mXMPBuffer.resize(len+1, 0);
             memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
             xmpAppears = true;
         } else if (!exifAppears &&
-                   len > kExifIdCode.size() &&
-                   !strncmp(reinterpret_cast<const char*>(marker->data),
-                            kExifIdCode.c_str(),
-                            kExifIdCode.size())) {
+                   len > sizeof(kExifIdCode) &&
+                   !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
             mEXIFBuffer.resize(len, 0);
             memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
             exifAppears = true;
+            mExifPos = pos - marker->original_length;
         } else if (!iccAppears &&
                    len > sizeof(kICCSig) &&
                    !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
@@ -216,21 +267,25 @@
         }
     }
 
-    if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) {
-        // constraint on max width and max height is only due to alloc constraints
-        // tune these values basing on the target device
+    mWidth = cinfo.image_width;
+    mHeight = cinfo.image_height;
+    if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
         status = false;
         goto CleanUp;
     }
 
-    mWidth = cinfo.image_width;
-    mHeight = cinfo.image_height;
-
     if (decodeToRGBA) {
-        if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
-            // We don't intend to support decoding grayscale to RGBA
+        // The primary image is expected to be yuv420 sampling
+        if (cinfo.jpeg_color_space != JCS_YCbCr) {
             status = false;
-            ALOGE("%s: decoding grayscale to RGBA is unsupported", __func__);
+            ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
+            goto CleanUp;
+        }
+        if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+            cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+            cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
+            status = false;
+            ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
             goto CleanUp;
         }
         // 4 bytes per pixel
@@ -238,12 +293,9 @@
         cinfo.out_color_space = JCS_EXT_RGBA;
     } else {
         if (cinfo.jpeg_color_space == JCS_YCbCr) {
-            if (cinfo.comp_info[0].h_samp_factor != 2 ||
-                cinfo.comp_info[1].h_samp_factor != 1 ||
-                cinfo.comp_info[2].h_samp_factor != 1 ||
-                cinfo.comp_info[0].v_samp_factor != 2 ||
-                cinfo.comp_info[1].v_samp_factor != 1 ||
-                cinfo.comp_info[2].v_samp_factor != 1) {
+            if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+                cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+                cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
                 status = false;
                 ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
                 goto CleanUp;
@@ -251,17 +303,19 @@
             mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
         } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
             mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+        } else {
+            status = false;
+            ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__);
+            goto CleanUp;
         }
         cinfo.out_color_space = cinfo.jpeg_color_space;
         cinfo.raw_data_out = TRUE;
     }
 
-    cinfo.dct_method = JDCT_IFAST;
-
+    cinfo.dct_method = JDCT_ISLOW;
     jpeg_start_decompress(&cinfo);
-
     if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
-            cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+                    cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
         status = false;
         goto CleanUp;
     }
@@ -274,25 +328,20 @@
 }
 
 bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
-        bool isSingleChannel) {
-    if (isSingleChannel) {
-        return decompressSingleChannel(cinfo, dest);
-    }
-    if (cinfo->out_color_space == JCS_EXT_RGBA)
-        return decompressRGBA(cinfo, dest);
-    else
-        return decompressYUV(cinfo, dest);
+                                   bool isSingleChannel) {
+    return isSingleChannel
+            ? decompressSingleChannel(cinfo, dest)
+            : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
+                                                        : decompressYUV(cinfo, dest));
 }
 
-bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length,
-                              size_t *pWidth, size_t *pHeight,
-                              std::vector<uint8_t> *iccData , std::vector<uint8_t> *exifData) {
+bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+                                                     size_t* pHeight, std::vector<uint8_t>* iccData,
+                                                     std::vector<uint8_t>* exifData) {
     jpeg_decompress_struct cinfo;
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
     cinfo.err = jpeg_std_error(&myerr.pub);
     myerr.pub.error_exit = jpegrerror_exit;
-
     if (setjmp(myerr.setjmp_buffer)) {
         jpeg_destroy_decompress(&cinfo);
         return false;
@@ -302,6 +351,7 @@
     jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
     jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
 
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     cinfo.src = &mgr;
     if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
         jpeg_destroy_decompress(&cinfo);
@@ -316,8 +366,7 @@
     }
 
     if (iccData != nullptr) {
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker;
-             marker = marker->next) {
+        for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
             if (marker->marker != kAPP2Marker) {
                 continue;
             }
@@ -339,9 +388,8 @@
             }
 
             const unsigned int len = marker->data_length;
-            if (len >= kExifIdCode.size() &&
-                !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
-                         kExifIdCode.size())) {
+            if (len >= sizeof(kExifIdCode) &&
+                !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
                 exifData->resize(len, 0);
                 memcpy(static_cast<void*>(exifData->data()), marker->data, len);
                 exifAppears = true;
@@ -354,25 +402,20 @@
 }
 
 bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPLE* decodeDst = (JSAMPLE*) dest;
-    uint32_t lines = 0;
-    // TODO: use batches for more effectiveness
-    while (lines < cinfo->image_height) {
-        uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1);
-        if (ret == 0) {
-            break;
-        }
-        decodeDst += cinfo->image_width * 4;
-        lines++;
+    JSAMPLE* out = (JSAMPLE*)dest;
+
+    while (cinfo->output_scanline < cinfo->image_height) {
+        if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
+        out += cinfo->image_width * 4;
     }
-    return lines == cinfo->image_height;
+    return true;
 }
 
 bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
     JSAMPROW y[kCompressBatchSize];
     JSAMPROW cb[kCompressBatchSize / 2];
     JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3] {y, cb, cr};
+    JSAMPARRAY planes[3]{y, cb, cr};
 
     size_t y_plane_size = cinfo->image_width * cinfo->image_height;
     size_t uv_plane_size = y_plane_size / 4;
@@ -391,7 +434,7 @@
     JSAMPROW y_intrm[kCompressBatchSize];
     JSAMPROW cb_intrm[kCompressBatchSize / 2];
     JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3] {y_intrm, cb_intrm, cr_intrm};
+    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
     if (!is_width_aligned) {
         size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
@@ -448,9 +491,10 @@
     return true;
 }
 
-bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo,
+                                                const uint8_t* dest) {
     JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1] {y};
+    JSAMPARRAY planes[1]{y};
 
     uint8_t* y_plane = const_cast<uint8_t*>(dest);
     std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
@@ -461,7 +505,7 @@
     std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
     uint8_t* y_plane_intrm = nullptr;
     JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[1] {y_intrm};
+    JSAMPARRAY planes_intrm[1]{y_intrm};
     if (!is_width_aligned) {
         size_t mcu_row_size = aligned_width * kCompressBatchSize;
         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
@@ -496,4 +540,4 @@
     return true;
 }
 
-} // namespace ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
index a03547b..13ae742 100644
--- a/libs/ultrahdr/jpegencoderhelper.cpp
+++ b/libs/ultrahdr/jpegencoderhelper.cpp
@@ -14,38 +14,35 @@
  * limitations under the License.
  */
 
+#include <cstring>
+#include <memory>
+#include <vector>
+
 #include <ultrahdr/jpegencoderhelper.h>
-
 #include <utils/Log.h>
 
-#include <errno.h>
-
 namespace android::ultrahdr {
 
-#define ALIGNM(x, m)  ((((x) + ((m) - 1)) / (m)) * (m))
-
 // The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
 struct destination_mgr {
-public:
     struct jpeg_destination_mgr mgr;
     JpegEncoderHelper* encoder;
 };
 
-JpegEncoderHelper::JpegEncoderHelper() {
-}
+JpegEncoderHelper::JpegEncoderHelper() {}
 
-JpegEncoderHelper::~JpegEncoderHelper() {
-}
+JpegEncoderHelper::~JpegEncoderHelper() {}
 
-bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
-                                   const void* iccBuffer, unsigned int iccSize,
-                                   bool isSingleChannel) {
+bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
+                                      int height, int lumaStride, int chromaStride, int quality,
+                                      const void* iccBuffer, unsigned int iccSize) {
     mResultBuffer.clear();
-    if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
+    if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer,
+                iccSize)) {
         return false;
     }
-    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
-        (width * height * 12) / 8, width, height, mResultBuffer.size());
+    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height,
+          mResultBuffer.size());
     return true;
 }
 
@@ -85,29 +82,28 @@
     char buffer[JMSG_LENGTH_MAX];
 
     /* Create the message */
-    (*cinfo->err->format_message) (cinfo, buffer);
+    (*cinfo->err->format_message)(cinfo, buffer);
     ALOGE("%s\n", buffer);
 }
 
-bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality,
-                         const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
+bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
+                               int height, int lumaStride, int chromaStride, int quality,
+                               const void* iccBuffer, unsigned int iccSize) {
     jpeg_compress_struct cinfo;
     jpeg_error_mgr jerr;
 
     cinfo.err = jpeg_std_error(&jerr);
-    // Override output_message() to print error log with ALOGE().
     cinfo.err->output_message = &outputErrorMessage;
     jpeg_create_compress(&cinfo);
     setJpegDestination(&cinfo);
-
-    setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
+    setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr);
     jpeg_start_compress(&cinfo, TRUE);
-
     if (iccBuffer != nullptr && iccSize > 0) {
         jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
     }
-
-    bool status = compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel);
+    bool status = cinfo.num_components == 1
+            ? compressY(&cinfo, yBuffer, lumaStride)
+            : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride);
     jpeg_finish_compress(&cinfo);
     jpeg_destroy_compress(&cinfo);
 
@@ -115,8 +111,9 @@
 }
 
 void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
-    destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
-            (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
+    destination_mgr* dest = static_cast<struct destination_mgr*>(
+            (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT,
+                                       sizeof(destination_mgr)));
     dest->encoder = this;
     dest->mgr.init_destination = &initDestination;
     dest->mgr.empty_output_buffer = &emptyOutputBuffer;
@@ -125,59 +122,40 @@
 }
 
 void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
-                                        jpeg_compress_struct* cinfo, bool isSingleChannel) {
+                                              jpeg_compress_struct* cinfo, bool isSingleChannel) {
     cinfo->image_width = width;
     cinfo->image_height = height;
-    if (isSingleChannel) {
-        cinfo->input_components = 1;
-        cinfo->in_color_space = JCS_GRAYSCALE;
-    } else {
-        cinfo->input_components = 3;
-        cinfo->in_color_space = JCS_YCbCr;
-    }
+    cinfo->input_components = isSingleChannel ? 1 : 3;
+    cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr;
     jpeg_set_defaults(cinfo);
-
     jpeg_set_quality(cinfo, quality, TRUE);
-    jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
     cinfo->raw_data_in = TRUE;
-    cinfo->dct_method = JDCT_IFAST;
-
-    if (!isSingleChannel) {
-        // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
-        // source format is YUV420.
-        cinfo->comp_info[0].h_samp_factor = 2;
-        cinfo->comp_info[0].v_samp_factor = 2;
-        cinfo->comp_info[1].h_samp_factor = 1;
-        cinfo->comp_info[1].v_samp_factor = 1;
-        cinfo->comp_info[2].h_samp_factor = 1;
-        cinfo->comp_info[2].v_samp_factor = 1;
+    cinfo->dct_method = JDCT_ISLOW;
+    cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
+    cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
+    for (int i = 1; i < cinfo->num_components; i++) {
+        cinfo->comp_info[i].h_samp_factor = 1;
+        cinfo->comp_info[i].v_samp_factor = 1;
     }
 }
 
-bool JpegEncoderHelper::compress(
-        jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
-    if (isSingleChannel) {
-        return compressSingleChannel(cinfo, image);
-    }
-    return compressYuv(cinfo, image);
-}
-
-bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
+                                    const uint8_t* uvBuffer, int lumaStride, int chromaStride) {
     JSAMPROW y[kCompressBatchSize];
     JSAMPROW cb[kCompressBatchSize / 2];
     JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3] {y, cb, cr};
+    JSAMPARRAY planes[3]{y, cb, cr};
 
-    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
-    size_t uv_plane_size = y_plane_size / 4;
-    uint8_t* y_plane = const_cast<uint8_t*>(yuv);
-    uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
-    uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
+    size_t y_plane_size = lumaStride * cinfo->image_height;
+    size_t u_plane_size = chromaStride * cinfo->image_height / 2;
+    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
+    uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer);
+    uint8_t* v_plane = const_cast<uint8_t*>(u_plane + u_plane_size);
     std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
     memset(empty.get(), 0, cinfo->image_width);
 
     const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool is_width_aligned = (aligned_width == cinfo->image_width);
+    const bool need_padding = (lumaStride < aligned_width);
     std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
     uint8_t* y_plane_intrm = nullptr;
     uint8_t* u_plane_intrm = nullptr;
@@ -186,7 +164,7 @@
     JSAMPROW cb_intrm[kCompressBatchSize / 2];
     JSAMPROW cr_intrm[kCompressBatchSize / 2];
     JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (!is_width_aligned) {
+    if (need_padding) {
         size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
         y_plane_intrm = buffer_intrm.get();
@@ -211,11 +189,11 @@
         for (int i = 0; i < kCompressBatchSize; ++i) {
             size_t scanline = cinfo->next_scanline + i;
             if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
+                y[i] = y_plane + scanline * lumaStride;
             } else {
                 y[i] = empty.get();
             }
-            if (!is_width_aligned) {
+            if (need_padding) {
                 memcpy(y_intrm[i], y[i], cinfo->image_width);
             }
         }
@@ -223,18 +201,18 @@
         for (int i = 0; i < kCompressBatchSize / 2; ++i) {
             size_t scanline = cinfo->next_scanline / 2 + i;
             if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * (cinfo->image_width / 2);
+                int offset = scanline * chromaStride;
                 cb[i] = u_plane + offset;
                 cr[i] = v_plane + offset;
             } else {
                 cb[i] = cr[i] = empty.get();
             }
-            if (!is_width_aligned) {
+            if (need_padding) {
                 memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
                 memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
             }
         }
-        int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
                                             kCompressBatchSize);
         if (processed != kCompressBatchSize) {
             ALOGE("Number of processed lines does not equal input lines.");
@@ -244,22 +222,23 @@
     return true;
 }
 
-bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
+                                  int lumaStride) {
     JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1] {y};
+    JSAMPARRAY planes[1]{y};
 
-    uint8_t* y_plane = const_cast<uint8_t*>(image);
+    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
     std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
     memset(empty.get(), 0, cinfo->image_width);
 
     const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
+    const bool need_padding = (lumaStride < aligned_width);
     std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
     uint8_t* y_plane_intrm = nullptr;
     uint8_t* u_plane_intrm = nullptr;
     JSAMPROW y_intrm[kCompressBatchSize];
     JSAMPARRAY planes_intrm[]{y_intrm};
-    if (!is_width_aligned) {
+    if (need_padding) {
         size_t mcu_row_size = aligned_width * kCompressBatchSize;
         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
         y_plane_intrm = buffer_intrm.get();
@@ -273,15 +252,15 @@
         for (int i = 0; i < kCompressBatchSize; ++i) {
             size_t scanline = cinfo->next_scanline + i;
             if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
+                y[i] = y_plane + scanline * lumaStride;
             } else {
                 y[i] = empty.get();
             }
-            if (!is_width_aligned) {
+            if (need_padding) {
                 memcpy(y_intrm[i], y[i], cinfo->image_width);
             }
         }
-        int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
                                             kCompressBatchSize);
         if (processed != kCompressBatchSize / 2) {
             ALOGE("Number of processed lines does not equal input lines.");
@@ -291,4 +270,4 @@
     return true;
 }
 
-} // namespace ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index fb24c9d..3d70fce 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -14,31 +14,26 @@
  * limitations under the License.
  */
 
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegencoderhelper.h>
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegrutils.h>
-#include <ultrahdr/multipictureformat.h>
-#include <ultrahdr/icc.h>
-
-#include <image_io/jpeg/jpeg_marker.h>
-#include <image_io/jpeg/jpeg_info.h>
-#include <image_io/jpeg/jpeg_scanner.h>
-#include <image_io/jpeg/jpeg_info_builder.h>
-#include <image_io/base/data_segment_data_source.h>
-#include <utils/Log.h>
-
-#include <map>
-#include <memory>
-#include <sstream>
-#include <string>
 #include <cmath>
 #include <condition_variable>
 #include <deque>
+#include <memory>
 #include <mutex>
 #include <thread>
-#include <unistd.h>
+
+#include <ultrahdr/gainmapmath.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegrutils.h>
+#include <ultrahdr/multipictureformat.h>
+
+#include <image_io/base/data_segment_data_source.h>
+#include <image_io/jpeg/jpeg_info.h>
+#include <image_io/jpeg/jpeg_info_builder.h>
+#include <image_io/jpeg/jpeg_marker.h>
+#include <image_io/jpeg/jpeg_scanner.h>
+
+#include <utils/Log.h>
 
 using namespace std;
 using namespace photos_editing_formats::image_io;
@@ -60,25 +55,6 @@
     }                           \
   }
 
-// The current JPEGR version that we encode to
-static const char* const kJpegrVersion = "1.0";
-
-// Map is quarter res / sixteenth size
-static const size_t kMapDimensionScaleFactor = 4;
-
-// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
-// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
-// 1 sample is sufficient. We are using 2 here anyways
-static const int kMinWidth = 2 * kMapDimensionScaleFactor;
-static const int kMinHeight = 2 * kMapDimensionScaleFactor;
-
-// JPEG block size.
-// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma,
-// and 8 x 8 for chroma.
-// Width must be 16 dividable for luma, and 8 dividable for chroma.
-// If this criteria is not facilitated, we will pad zeros based to each line on the
-// required block size.
-static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
 // JPEG compress quality (0 ~ 100) for gain map
 static const int kMapCompressQuality = 85;
 
@@ -96,184 +72,200 @@
   return cpuCoreCount;
 }
 
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
-                                       jr_uncompressed_ptr uncompressed_yuv_420_image,
+/*
+ * Helper function copies the JPEG image from without EXIF.
+ *
+ * @param pDest destination of the data to be written.
+ * @param pSource source of data being written.
+ * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
+ *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
+ * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
+ */
+static void copyJpegWithoutExif(jr_compressed_ptr pDest,
+                                jr_compressed_ptr pSource,
+                                size_t exif_pos,
+                                size_t exif_size) {
+  const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
+  pDest->length = pSource->length - exif_size - exif_offset;
+  pDest->data = new uint8_t[pDest->length];
+  pDest->maxLength = pDest->length;
+  pDest->colorGamut = pSource->colorGamut;
+  memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
+  memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
+         (uint8_t*)pSource->data + exif_pos + exif_size,
+         pSource->length - exif_pos - exif_size);
+}
+
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+                                       jr_uncompressed_ptr yuv420_image_ptr,
                                        ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest) {
-  if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) {
-    ALOGE("received nullptr for uncompressed p010 image");
+                                       jr_compressed_ptr dest_ptr) {
+  if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
+    ALOGE("Received nullptr for input p010 image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (uncompressed_p010_image->width % 2 != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image dimensions cannot be odd, image dimensions %dx%d",
-          uncompressed_p010_image->width, uncompressed_p010_image->height);
+  if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
+    ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
+          p010_image_ptr->height);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (uncompressed_p010_image->width < kMinWidth
-          || uncompressed_p010_image->height < kMinHeight) {
-    ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d",
-          kMinWidth, kMinHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
+  if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
+    ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
+          kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (uncompressed_p010_image->width > kMaxWidth
-          || uncompressed_p010_image->height > kMaxHeight) {
-    ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d",
-          kMaxWidth, kMaxHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
+  if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
+    ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
+          kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
-          || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut);
+  if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+      p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+    ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (uncompressed_p010_image->luma_stride != 0
-          && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
-    ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d",
-                uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
+  if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
+    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
+          p010_image_ptr->luma_stride, p010_image_ptr->width);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (uncompressed_p010_image->chroma_data != nullptr
-          && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) {
-    ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d",
-          uncompressed_p010_image->chroma_stride,
-          uncompressed_p010_image->width);
+  if (p010_image_ptr->chroma_data != nullptr &&
+      p010_image_ptr->chroma_stride < p010_image_ptr->width) {
+    ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
+          p010_image_ptr->chroma_stride, p010_image_ptr->width);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for destination");
+  if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
+    ALOGE("Received nullptr for destination");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX
-          || hdr_tf == ULTRAHDR_TF_SRGB) {
+  if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
     ALOGE("Invalid hdr transfer function %d", hdr_tf);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  if (uncompressed_yuv_420_image == nullptr) {
+  if (yuv420_image_ptr == nullptr) {
     return NO_ERROR;
   }
-
-  if (uncompressed_yuv_420_image->data == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
+  if (yuv420_image_ptr->data == nullptr) {
+    ALOGE("Received nullptr for uncompressed 420 image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (uncompressed_yuv_420_image->luma_stride != 0) {
-    ALOGE("Stride is not supported for YUV420 image");
-    return ERROR_JPEGR_UNSUPPORTED_FEATURE;
-  }
-
-  if (uncompressed_yuv_420_image->chroma_data != nullptr) {
-    ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must"
-          "be immediately after the luma data.");
-    return ERROR_JPEGR_UNSUPPORTED_FEATURE;
-  }
-
-  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
-      || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
-    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
-              uncompressed_p010_image->width,
-              uncompressed_p010_image->height,
-              uncompressed_yuv_420_image->width,
-              uncompressed_yuv_420_image->height);
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-
-  if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
-          || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut);
+  if (yuv420_image_ptr->luma_stride != 0 &&
+      yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
+    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
+          yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
+  if (yuv420_image_ptr->chroma_data != nullptr &&
+      yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
+    ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
+          yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+  if (p010_image_ptr->width != yuv420_image_ptr->width ||
+      p010_image_ptr->height != yuv420_image_ptr->height) {
+    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
+          p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+  if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+      yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+    ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
   return NO_ERROR;
 }
 
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
-                                       jr_uncompressed_ptr uncompressed_yuv_420_image,
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+                                       jr_uncompressed_ptr yuv420_image_ptr,
                                        ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest,
-                                       int quality) {
-  if (status_t ret = areInputArgumentsValid(
-          uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
-    return ret;
-  }
-
+                                       jr_compressed_ptr dest_ptr, int quality) {
   if (quality < 0 || quality > 100) {
     ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
-  return NO_ERROR;
+  return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
 }
 
 /* Encode API-0 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                            ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest,
-                            int quality,
-                            jr_exif_ptr exif) {
-  if (status_t ret = areInputArgumentsValid(
-          uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
-          hdr_tf, dest, quality) != NO_ERROR) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
+  // validate input arguments
+  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
+      ret != NO_ERROR) {
     return ret;
   }
-
   if (exif != nullptr && exif->data == nullptr) {
     ALOGE("received nullptr for exif metadata");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  ultrahdr_metadata_struct metadata;
-  metadata.version = kJpegrVersion;
+  // clean up input structure for later usage
+  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+  if (!p010_image.chroma_data) {
+    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+    p010_image.chroma_stride = p010_image.luma_stride;
+  }
 
-  jpegr_uncompressed_struct uncompressed_yuv_420_image;
-  unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
-      uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
-  uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
-  JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
+  const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
+  unique_ptr<uint8_t[]> yuv420_image_data =
+          make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
+  jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
+                                            .width = p010_image.width,
+                                            .height = p010_image.height,
+                                            .colorGamut = p010_image.colorGamut,
+                                            .luma_stride = yu420_luma_stride,
+                                            .chroma_data = nullptr,
+                                            .chroma_stride = yu420_luma_stride >> 1};
+  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
 
-  jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateGainMap(
-      &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
+  // tone map
+  JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
+
+  // gain map
+  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+  jpegr_uncompressed_struct gainmap_image;
+  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
   std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
 
-  JpegEncoderHelper jpeg_encoder_gainmap;
-  JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
-  jpegr_compressed_struct compressed_map;
-  compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
-  compressed_map.length = compressed_map.maxLength;
-  compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
-  compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+  // compress gain map
+  JpegEncoderHelper jpeg_enc_obj_gm;
+  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+                                            .length = static_cast<int>(
+                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
+                                            .maxLength = static_cast<int>(
+                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
+                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
 
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                  uncompressed_yuv_420_image.colorGamut);
+  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
 
-  // Convert to Bt601 YUV encoding for JPEG encode
-  JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut,
-                         ULTRAHDR_COLORGAMUT_P3));
+  // convert to Bt601 YUV encoding for JPEG encode
+  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
+    JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
+  }
 
-  JpegEncoderHelper jpeg_encoder;
-  if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
-                                  uncompressed_yuv_420_image.width,
-                                  uncompressed_yuv_420_image.height, quality,
-                                  icc->getData(), icc->getLength())) {
+  // compress 420 image
+  JpegEncoderHelper jpeg_enc_obj_yuv420;
+  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
+                                         reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
+                                         yuv420_image.width, yuv420_image.height,
+                                         yuv420_image.luma_stride, yuv420_image.chroma_stride,
+                                         quality, icc->getData(), icc->getLength())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
-  jpegr_compressed_struct jpeg;
-  jpeg.data = jpeg_encoder.getCompressedImagePtr();
-  jpeg.length = jpeg_encoder.getCompressedImageSize();
+  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
+                                  .length = static_cast<int>(
+                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
+                                  .maxLength = static_cast<int>(
+                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
+                                  .colorGamut = yuv420_image.colorGamut};
 
-  // No ICC since JPEG encode already did it
+  // append gain map, no ICC since JPEG encode already did it
   JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
                             &metadata, dest));
 
@@ -281,226 +273,277 @@
 }
 
 /* Encode API-1 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                            jr_uncompressed_ptr uncompressed_yuv_420_image,
-                            ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest,
-                            int quality,
-                            jr_exif_ptr exif) {
-  if (uncompressed_yuv_420_image == nullptr) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+                            jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
+                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
+  // validate input arguments
+  if (yuv420_image_ptr == nullptr) {
     ALOGE("received nullptr for uncompressed 420 image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   if (exif != nullptr && exif->data == nullptr) {
     ALOGE("received nullptr for exif metadata");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (status_t ret = areInputArgumentsValid(
-          uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf,
-          dest, quality) != NO_ERROR) {
+  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
+      ret != NO_ERROR) {
     return ret;
   }
 
-  ultrahdr_metadata_struct metadata;
-  metadata.version = kJpegrVersion;
+  // clean up input structure for later usage
+  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+  if (!p010_image.chroma_data) {
+    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+    p010_image.chroma_stride = p010_image.luma_stride;
+  }
+  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
+  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+  if (!yuv420_image.chroma_data) {
+    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+    yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+  }
 
-  jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateGainMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
+  // gain map
+  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+  jpegr_uncompressed_struct gainmap_image;
+  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
   std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
 
-  JpegEncoderHelper jpeg_encoder_gainmap;
-  JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
-  jpegr_compressed_struct compressed_map;
-  compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
-  compressed_map.length = compressed_map.maxLength;
-  compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
-  compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+  // compress gain map
+  JpegEncoderHelper jpeg_enc_obj_gm;
+  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+                                            .length = static_cast<int>(
+                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
+                                            .maxLength = static_cast<int>(
+                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
+                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
 
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                  uncompressed_yuv_420_image->colorGamut);
+  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
 
-  // Convert to Bt601 YUV encoding for JPEG encode; make a copy so as to no clobber client data
-  unique_ptr<uint8_t[]> yuv_420_bt601_data = make_unique<uint8_t[]>(
-      uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
-  memcpy(yuv_420_bt601_data.get(), uncompressed_yuv_420_image->data,
-         uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
+  jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
+  unique_ptr<uint8_t[]> yuv_420_bt601_data;
+  // Convert to bt601 YUV encoding for JPEG encode
+  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
+    const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
+    yuv_420_bt601_data =
+            make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
+    yuv420_bt601_image.data = yuv_420_bt601_data.get();
+    yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
+    yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
+    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
+    yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
+    yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
 
-  jpegr_uncompressed_struct yuv_420_bt601_image = {
-    yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height,
-    uncompressed_yuv_420_image->colorGamut };
-  JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut,
-                         ULTRAHDR_COLORGAMUT_P3));
+    {
+      // copy luma
+      uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
+      uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
+      if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
+        memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
+      } else {
+        for (size_t i = 0; i < yuv420_image.height; i++) {
+          memcpy(y_dst, y_src, yuv420_image.width);
+          if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
+            memset(y_dst + yuv420_image.width, 0,
+                   yuv420_bt601_image.luma_stride - yuv420_image.width);
+          }
+          y_dst += yuv420_bt601_image.luma_stride;
+          y_src += yuv420_image.luma_stride;
+        }
+      }
+    }
 
-  JpegEncoderHelper jpeg_encoder;
-  if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data,
-                                  yuv_420_bt601_image.width,
-                                  yuv_420_bt601_image.height, quality,
-                                  icc->getData(), icc->getLength())) {
+    if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
+      // copy luma
+      uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
+      uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
+      memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
+    } else {
+      // copy cb & cr
+      uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
+      uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
+      uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
+      uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
+      for (size_t i = 0; i < yuv420_image.height / 2; i++) {
+        memcpy(cb_dst, cb_src, yuv420_image.width / 2);
+        memcpy(cr_dst, cr_src, yuv420_image.width / 2);
+        if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
+          memset(cb_dst + yuv420_image.width / 2, 0,
+                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
+          memset(cr_dst + yuv420_image.width / 2, 0,
+                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
+        }
+        cb_dst += yuv420_bt601_image.chroma_stride;
+        cb_src += yuv420_image.chroma_stride;
+        cr_dst += yuv420_bt601_image.chroma_stride;
+        cr_src += yuv420_image.chroma_stride;
+      }
+    }
+    JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
+  }
+
+  // compress 420 image
+  JpegEncoderHelper jpeg_enc_obj_yuv420;
+  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
+                                         reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
+                                         yuv420_bt601_image.width, yuv420_bt601_image.height,
+                                         yuv420_bt601_image.luma_stride,
+                                         yuv420_bt601_image.chroma_stride, quality, icc->getData(),
+                                         icc->getLength())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
-  jpegr_compressed_struct jpeg;
-  jpeg.data = jpeg_encoder.getCompressedImagePtr();
-  jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  // No ICC since jpeg encode already did it
+  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
+                                  .length = static_cast<int>(
+                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
+                                  .maxLength = static_cast<int>(
+                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
+                                  .colorGamut = yuv420_image.colorGamut};
+
+  // append gain map, no ICC since JPEG encode already did it
   JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
                             &metadata, dest));
-
   return NO_ERROR;
 }
 
 /* Encode API-2 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                            jr_uncompressed_ptr uncompressed_yuv_420_image,
-                            jr_compressed_ptr compressed_jpeg_image,
-                            ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest) {
-  if (uncompressed_yuv_420_image == nullptr) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+                            jr_uncompressed_ptr yuv420_image_ptr,
+                            jr_compressed_ptr yuv420jpg_image_ptr,
+                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
+  // validate input arguments
+  if (yuv420_image_ptr == nullptr) {
     ALOGE("received nullptr for uncompressed 420 image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
     ALOGE("received nullptr for compressed jpeg image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (status_t ret = areInputArgumentsValid(
-          uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
+  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
+      ret != NO_ERROR) {
     return ret;
   }
 
-  ultrahdr_metadata_struct metadata;
-  metadata.version = kJpegrVersion;
-
-  jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateGainMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
-
-  JpegEncoderHelper jpeg_encoder_gainmap;
-  JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
-  jpegr_compressed_struct compressed_map;
-  compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
-  compressed_map.length = compressed_map.maxLength;
-  compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
-  compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
-  // We just want to check if ICC is present, so don't do a full decode. Note,
-  // this doesn't verify that the ICC is valid.
-  JpegDecoderHelper decoder;
-  std::vector<uint8_t> icc;
-  decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
-                                       /* pWidth */ nullptr, /* pHeight */ nullptr,
-                                       &icc, /* exifData */ nullptr);
-
-  // Add ICC if not already present.
-  if (icc.size() > 0) {
-      JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
-                                /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
-  } else {
-      sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                         uncompressed_yuv_420_image->colorGamut);
-      JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
-                                newIcc->getData(), newIcc->getLength(), &metadata, dest));
+  // clean up input structure for later usage
+  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+  if (!p010_image.chroma_data) {
+    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+    p010_image.chroma_stride = p010_image.luma_stride;
+  }
+  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
+  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+  if (!yuv420_image.chroma_data) {
+    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
+    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
   }
 
-  return NO_ERROR;
+  // gain map
+  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+  jpegr_uncompressed_struct gainmap_image;
+  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
+  std::unique_ptr<uint8_t[]> map_data;
+  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+  // compress gain map
+  JpegEncoderHelper jpeg_enc_obj_gm;
+  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+                                              .length = static_cast<int>(
+                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
+                                              .maxLength = static_cast<int>(
+                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
+                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
 }
 
 /* Encode API-3 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                            jr_compressed_ptr compressed_jpeg_image,
-                            ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest) {
-  if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+                            jr_compressed_ptr yuv420jpg_image_ptr,
+                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
+  // validate input arguments
+  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
     ALOGE("received nullptr for compressed jpeg image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (status_t ret = areInputArgumentsValid(
-          uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
-          hdr_tf, dest) != NO_ERROR) {
+  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
     return ret;
   }
 
-  // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode.
-  JpegDecoderHelper jpeg_decoder;
-  if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
+  // clean up input structure for later usage
+  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+  if (!p010_image.chroma_data) {
+    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+    p010_image.chroma_stride = p010_image.luma_stride;
+  }
+
+  // decode input jpeg, gamut is going to be bt601.
+  JpegDecoderHelper jpeg_dec_obj_yuv420;
+  if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
+                                           yuv420jpg_image_ptr->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
-  jpegr_uncompressed_struct uncompressed_yuv_420_image;
-  uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
-  uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
-  uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
-  uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
+  jpegr_uncompressed_struct yuv420_image{};
+  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
+  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+  yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
+  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+  if (!yuv420_image.chroma_data) {
+    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
+    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+  }
 
-  if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
-   || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
+  if (p010_image_ptr->width != yuv420_image.width ||
+      p010_image_ptr->height != yuv420_image.height) {
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
-  ultrahdr_metadata_struct metadata;
-  metadata.version = kJpegrVersion;
-
-  jpegr_uncompressed_struct map;
-  // Indicate that the SDR image is Bt.601 YUV encoded.
-  JPEGR_CHECK(generateGainMap(
-      &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map,
-      true /* sdr_is_601 */ ));
+  // gain map
+  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+  jpegr_uncompressed_struct gainmap_image;
+  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
+                              true /* sdr_is_601 */));
   std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
 
-  JpegEncoderHelper jpeg_encoder_gainmap;
-  JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
-  jpegr_compressed_struct compressed_map;
-  compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
-  compressed_map.length = compressed_map.maxLength;
-  compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
-  compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+  // compress gain map
+  JpegEncoderHelper jpeg_enc_obj_gm;
+  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+                                              .length = static_cast<int>(
+                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
+                                              .maxLength = static_cast<int>(
+                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
+                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
 
-  // We just want to check if ICC is present, so don't do a full decode. Note,
-  // this doesn't verify that the ICC is valid.
-  JpegDecoderHelper decoder;
-  std::vector<uint8_t> icc;
-  decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
-                                       /* pWidth */ nullptr, /* pHeight */ nullptr,
-                                       &icc, /* exifData */ nullptr);
-
-  // Add ICC if not already present.
-  if (icc.size() > 0) {
-      JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
-                                /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
-  } else {
-      sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                         uncompressed_yuv_420_image.colorGamut);
-      JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
-                                newIcc->getData(), newIcc->getLength(), &metadata, dest));
-  }
-
-  return NO_ERROR;
+  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
 }
 
 /* Encode API-4 */
-status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
-                            jr_compressed_ptr compressed_gainmap,
-                            ultrahdr_metadata_ptr metadata,
+status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
+                            jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
                             jr_compressed_ptr dest) {
-  if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
     ALOGE("received nullptr for compressed jpeg image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) {
+  if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
     ALOGE("received nullptr for compressed gain map");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   if (dest == nullptr || dest->data == nullptr) {
     ALOGE("received nullptr for destination");
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -510,46 +553,46 @@
   // this doesn't verify that the ICC is valid.
   JpegDecoderHelper decoder;
   std::vector<uint8_t> icc;
-  decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
-                                       /* pWidth */ nullptr, /* pHeight */ nullptr,
-                                       &icc, /* exifData */ nullptr);
+  decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
+                                       /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
+                                       /* exifData */ nullptr);
 
   // Add ICC if not already present.
   if (icc.size() > 0) {
-      JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
-                                /* icc */ nullptr, /* icc size */ 0, metadata, dest));
+    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
+                              /* icc */ nullptr, /* icc size */ 0, metadata, dest));
   } else {
-      sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                         compressed_jpeg_image->colorGamut);
-      JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
-                                newIcc->getData(), newIcc->getLength(), metadata, dest));
+    sp<DataStruct> newIcc =
+            IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
+    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
+                              newIcc->getData(), newIcc->getLength(), metadata, dest));
   }
 
   return NO_ERROR;
 }
 
-status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
-  if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
+status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
+  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
     ALOGE("received nullptr for compressed jpegr image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (jpegr_info == nullptr) {
+  if (jpeg_image_info_ptr == nullptr) {
     ALOGE("received nullptr for compressed jpegr info struct");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   jpegr_compressed_struct primary_image, gainmap_image;
-  status_t status =
-      extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
+  status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
   if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
     return status;
   }
 
-  JpegDecoderHelper jpeg_decoder;
-  if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
-                                                 &jpegr_info->width, &jpegr_info->height,
-                                                 jpegr_info->iccData, jpegr_info->exifData)) {
+  JpegDecoderHelper jpeg_dec_obj_hdr;
+  if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
+                                                     &jpeg_image_info_ptr->width,
+                                                     &jpeg_image_info_ptr->height,
+                                                     jpeg_image_info_ptr->iccData,
+                                                     jpeg_image_info_ptr->exifData)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
@@ -557,41 +600,34 @@
 }
 
 /* Decode API */
-status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
-                            jr_uncompressed_ptr dest,
-                            float max_display_boost,
-                            jr_exif_ptr exif,
+status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
+                            float max_display_boost, jr_exif_ptr exif,
                             ultrahdr_output_format output_format,
-                            jr_uncompressed_ptr gain_map,
-                            ultrahdr_metadata_ptr metadata) {
-  if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
+                            jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
+  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
     ALOGE("received nullptr for compressed jpegr image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   if (dest == nullptr || dest->data == nullptr) {
     ALOGE("received nullptr for dest image");
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   if (max_display_boost < 1.0f) {
     ALOGE("received bad value for max_display_boost %f", max_display_boost);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
   if (exif != nullptr && exif->data == nullptr) {
     ALOGE("received nullptr address for exif data");
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
   if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
     ALOGE("received bad value for output format %d", output_format);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  jpegr_compressed_struct primary_image, gainmap_image;
+  jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
   status_t status =
-      extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
+          extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
   if (status != NO_ERROR) {
     if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
       ALOGE("received invalid compressed jpegr image");
@@ -599,22 +635,22 @@
     }
   }
 
-  JpegDecoderHelper jpeg_decoder;
-  if (!jpeg_decoder.decompressImage(primary_image.data, primary_image.length,
-                                    (output_format == ULTRAHDR_OUTPUT_SDR))) {
+  JpegDecoderHelper jpeg_dec_obj_yuv420;
+  if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
+                                           (output_format == ULTRAHDR_OUTPUT_SDR))) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
   if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    if ((jpeg_decoder.getDecompressedImageWidth() *
-         jpeg_decoder.getDecompressedImageHeight() * 4) >
-        jpeg_decoder.getDecompressedImageSize()) {
+    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
+        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
       return ERROR_JPEGR_CALCULATION_ERROR;
     }
   } else {
-    if ((jpeg_decoder.getDecompressedImageWidth() *
-         jpeg_decoder.getDecompressedImageHeight() * 3 / 2) >
-        jpeg_decoder.getDecompressedImageSize()) {
+    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
+        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
       return ERROR_JPEGR_CALCULATION_ERROR;
     }
   }
@@ -623,46 +659,46 @@
     if (exif->data == nullptr) {
       return ERROR_JPEGR_INVALID_NULL_PTR;
     }
-    if (exif->length < jpeg_decoder.getEXIFSize()) {
+    if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
       return ERROR_JPEGR_BUFFER_TOO_SMALL;
     }
-    memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
-    exif->length = jpeg_decoder.getEXIFSize();
+    memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
+    exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
   }
 
   if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    dest->width = jpeg_decoder.getDecompressedImageWidth();
-    dest->height = jpeg_decoder.getDecompressedImageHeight();
-    memcpy(dest->data, jpeg_decoder.getDecompressedImagePtr(), dest->width * dest->height * 4);
+    dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+    dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+    memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
+           dest->width * dest->height * 4);
     return NO_ERROR;
   }
 
-  JpegDecoderHelper gain_map_decoder;
-  if (!gain_map_decoder.decompressImage(gainmap_image.data, gainmap_image.length)) {
+  JpegDecoderHelper jpeg_dec_obj_gm;
+  if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
-  if ((gain_map_decoder.getDecompressedImageWidth() *
-       gain_map_decoder.getDecompressedImageHeight()) >
-      gain_map_decoder.getDecompressedImageSize()) {
+  if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
+      jpeg_dec_obj_gm.getDecompressedImageSize()) {
     return ERROR_JPEGR_CALCULATION_ERROR;
   }
 
-  jpegr_uncompressed_struct map;
-  map.data = gain_map_decoder.getDecompressedImagePtr();
-  map.width = gain_map_decoder.getDecompressedImageWidth();
-  map.height = gain_map_decoder.getDecompressedImageHeight();
+  jpegr_uncompressed_struct gainmap_image;
+  gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
+  gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
+  gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
 
-  if (gain_map != nullptr) {
-    gain_map->width = map.width;
-    gain_map->height = map.height;
-    int size = gain_map->width * gain_map->height;
-    gain_map->data = malloc(size);
-    memcpy(gain_map->data, map.data, size);
+  if (gainmap_image_ptr != nullptr) {
+    gainmap_image_ptr->width = gainmap_image.width;
+    gainmap_image_ptr->height = gainmap_image.height;
+    int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
+    gainmap_image_ptr->data = malloc(size);
+    memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
   }
 
   ultrahdr_metadata_struct uhdr_metadata;
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
-                          gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
+                          jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
     return ERROR_JPEGR_INVALID_METADATA;
   }
 
@@ -677,32 +713,33 @@
     metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
   }
 
-  jpegr_uncompressed_struct uncompressed_yuv_420_image;
-  uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
-  uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
-  uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
-  uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut(
-      jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize());
+  jpegr_uncompressed_struct yuv420_image;
+  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
+  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+  yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
+                                                         jpeg_dec_obj_yuv420.getICCSize());
+  yuv420_image.luma_stride = yuv420_image.width;
+  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+  yuv420_image.chroma_stride = yuv420_image.width >> 1;
 
-  JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
+  JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
                            max_display_boost, dest));
   return NO_ERROR;
 }
 
-status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
-                                JpegEncoderHelper* jpeg_encoder) {
-  if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) {
+status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
+                                JpegEncoderHelper* jpeg_enc_obj_ptr) {
+  if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // Don't need to convert YUV to Bt601 since single channel
-  if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
-                                   uncompressed_gain_map->width,
-                                   uncompressed_gain_map->height,
-                                   kMapCompressQuality,
-                                   nullptr,
-                                   0,
-                                   true /* isSingleChannel */)) {
+  if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
+                                       gainmap_image_ptr->width, gainmap_image_ptr->height,
+                                       gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
+                                       nullptr, 0)) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
 
@@ -714,13 +751,13 @@
               "align job size to kMapDimensionScaleFactor");
 
 class JobQueue {
- public:
+public:
   bool dequeueJob(size_t& rowStart, size_t& rowEnd);
   void enqueueJob(size_t rowStart, size_t rowEnd);
   void markQueueForEnd();
   void reset();
 
- private:
+private:
   bool mQueuedAllJobs = false;
   std::deque<std::tuple<size_t, size_t>> mJobs;
   std::mutex mMutex;
@@ -767,49 +804,48 @@
   mQueuedAllJobs = false;
 }
 
-status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                jr_uncompressed_ptr uncompressed_p010_image,
-                                ultrahdr_transfer_function hdr_tf,
-                                ultrahdr_metadata_ptr metadata,
-                                jr_uncompressed_ptr dest,
-                                bool sdr_is_601) {
-  if (uncompressed_yuv_420_image == nullptr
-   || uncompressed_p010_image == nullptr
-   || metadata == nullptr
-   || dest == nullptr) {
+status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+                                jr_uncompressed_ptr p010_image_ptr,
+                                ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
+                                jr_uncompressed_ptr dest, bool sdr_is_601) {
+  if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
+      dest == nullptr || yuv420_image_ptr->data == nullptr ||
+      yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
+      p010_image_ptr->chroma_data == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
-   || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
+  if (yuv420_image_ptr->width != p010_image_ptr->width ||
+      yuv420_image_ptr->height != p010_image_ptr->height) {
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
-
-  if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
-   || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+  if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+      p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
     return ERROR_JPEGR_INVALID_COLORGAMUT;
   }
 
-  size_t image_width = uncompressed_yuv_420_image->width;
-  size_t image_height = uncompressed_yuv_420_image->height;
+  size_t image_width = yuv420_image_ptr->width;
+  size_t image_height = yuv420_image_ptr->height;
   size_t map_width = image_width / kMapDimensionScaleFactor;
   size_t map_height = image_height / kMapDimensionScaleFactor;
-  size_t map_stride = static_cast<size_t>(
-          floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
-  size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
 
-  dest->width = map_stride;
-  dest->height = map_height_aligned;
+  dest->data = new uint8_t[map_width * map_height];
+  dest->width = map_width;
+  dest->height = map_height;
   dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  dest->data = new uint8_t[map_stride * map_height_aligned];
+  dest->luma_stride = map_width;
+  dest->chroma_data = nullptr;
+  dest->chroma_stride = 0;
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
 
   ColorTransformFn hdrInvOetf = nullptr;
-  float hdr_white_nits = kSdrWhiteNits;
+  float hdr_white_nits;
   switch (hdr_tf) {
     case ULTRAHDR_TF_LINEAR:
       hdrInvOetf = identityConversion;
+      // Note: this will produce clipping if the input exceeds kHlgMaxNits.
+      // TODO: TF LINEAR will be deprecated.
+      hdr_white_nits = kHlgMaxNits;
       break;
     case ULTRAHDR_TF_HLG:
 #if USE_HLG_INVOETF_LUT
@@ -843,12 +879,12 @@
   float log2MinBoost = log2(metadata->minContentBoost);
   float log2MaxBoost = log2(metadata->maxContentBoost);
 
-  ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
-      uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
+  ColorTransformFn hdrGamutConversionFn =
+          getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
 
   ColorCalculationFn luminanceFn = nullptr;
   ColorTransformFn sdrYuvToRgbFn = nullptr;
-  switch (uncompressed_yuv_420_image->colorGamut) {
+  switch (yuv420_image_ptr->colorGamut) {
     case ULTRAHDR_COLORGAMUT_BT709:
       luminanceFn = srgbLuminance;
       sdrYuvToRgbFn = srgbYuvToRgb;
@@ -870,7 +906,7 @@
   }
 
   ColorTransformFn hdrYuvToRgbFn = nullptr;
-  switch (uncompressed_p010_image->colorGamut) {
+  switch (p010_image_ptr->colorGamut) {
     case ULTRAHDR_COLORGAMUT_BT709:
       hdrYuvToRgbFn = srgbYuvToRgb;
       break;
@@ -890,18 +926,15 @@
   size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
   JobQueue jobQueue;
 
-  std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
-                                       metadata, dest, hdrInvOetf, hdrGamutConversionFn,
-                                       luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
-                                       log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
+  std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
+                                       hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
+                                       hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
+                                       &jobQueue]() -> void {
     size_t rowStart, rowEnd;
-    size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
-    size_t dest_map_stride = dest->width;
     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
       for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < dest_map_width; ++x) {
-          Color sdr_yuv_gamma =
-              sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
+        for (size_t x = 0; x < dest->width; ++x) {
+          Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
           Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
           // We are assuming the SDR input is always sRGB transfer.
 #if USE_SRGB_INVOETF_LUT
@@ -911,15 +944,15 @@
 #endif
           float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
 
-          Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+          Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
           Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
           Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
           hdr_rgb = hdrGamutConversionFn(hdr_rgb);
           float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
 
-          size_t pixel_idx = x + y * dest_map_stride;
+          size_t pixel_idx = x + y * dest->width;
           reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-              encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
+                  encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
         }
       }
     }
@@ -945,71 +978,64 @@
   return NO_ERROR;
 }
 
-status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                             jr_uncompressed_ptr uncompressed_gain_map,
-                             ultrahdr_metadata_ptr metadata,
-                             ultrahdr_output_format output_format,
-                             float max_display_boost,
+status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+                             jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
+                             ultrahdr_output_format output_format, float max_display_boost,
                              jr_uncompressed_ptr dest) {
-  if (uncompressed_yuv_420_image == nullptr
-   || uncompressed_gain_map == nullptr
-   || metadata == nullptr
-   || dest == nullptr) {
+  if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
+      dest == nullptr || yuv420_image_ptr->data == nullptr ||
+      yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (metadata->version.compare("1.0")) {
-      ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
-      return ERROR_JPEGR_UNSUPPORTED_METADATA;
+  if (metadata->version.compare(kJpegrVersion)) {
+    ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
+    return ERROR_JPEGR_UNSUPPORTED_METADATA;
   }
   if (metadata->gamma != 1.0f) {
-      ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
-      return ERROR_JPEGR_UNSUPPORTED_METADATA;
+    ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
+    return ERROR_JPEGR_UNSUPPORTED_METADATA;
   }
   if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
-      ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr,
-            metadata->offsetHdr);
-      return ERROR_JPEGR_UNSUPPORTED_METADATA;
+    ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
+    return ERROR_JPEGR_UNSUPPORTED_METADATA;
   }
-  if (metadata->hdrCapacityMin != metadata->minContentBoost
-   || metadata->hdrCapacityMax != metadata->maxContentBoost) {
-      ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
-            metadata->hdrCapacityMax);
-      return ERROR_JPEGR_UNSUPPORTED_METADATA;
+  if (metadata->hdrCapacityMin != metadata->minContentBoost ||
+      metadata->hdrCapacityMax != metadata->maxContentBoost) {
+    ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
+          metadata->hdrCapacityMax);
+    return ERROR_JPEGR_UNSUPPORTED_METADATA;
   }
 
   // TODO: remove once map scaling factor is computed based on actual map dims
-  size_t image_width = uncompressed_yuv_420_image->width;
-  size_t image_height = uncompressed_yuv_420_image->height;
+  size_t image_width = yuv420_image_ptr->width;
+  size_t image_height = yuv420_image_ptr->height;
   size_t map_width = image_width / kMapDimensionScaleFactor;
   size_t map_height = image_height / kMapDimensionScaleFactor;
-  map_width = static_cast<size_t>(
-          floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
-  map_height = ((map_height + 1) >> 1) << 1;
-  if (map_width != uncompressed_gain_map->width
-   || map_height != uncompressed_gain_map->height) {
-    ALOGE("gain map dimensions and primary image dimensions are not to scale");
+  if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
+    ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
+          "resolution is %dx%d, received gain map resolution is %dx%d",
+          (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  dest->width = uncompressed_yuv_420_image->width;
-  dest->height = uncompressed_yuv_420_image->height;
+  dest->width = yuv420_image_ptr->width;
+  dest->height = yuv420_image_ptr->height;
   ShepardsIDW idwTable(kMapDimensionScaleFactor);
   float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
   GainLUT gainLUT(metadata, display_boost);
 
   JobQueue jobQueue;
-  std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
-                                       metadata, dest, &jobQueue, &idwTable, output_format,
-                                       &gainLUT, display_boost]() -> void {
-    size_t width = uncompressed_yuv_420_image->width;
-    size_t height = uncompressed_yuv_420_image->height;
+  std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
+                                       &jobQueue, &idwTable, output_format, &gainLUT,
+                                       display_boost]() -> void {
+    size_t width = yuv420_image_ptr->width;
+    size_t height = yuv420_image_ptr->height;
 
     size_t rowStart, rowEnd;
     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
       for (size_t y = rowStart; y < rowEnd; ++y) {
         for (size_t x = 0; x < width; ++x) {
-          Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+          Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
           // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
           Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
           // We are assuming the SDR base image is always sRGB transfer.
@@ -1025,9 +1051,9 @@
           // Currently map_scale_factor is of type size_t, but it could be changed to a float
           // later.
           if (map_scale_factor != floorf(map_scale_factor)) {
-            gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
+            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
           } else {
-            gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
+            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
           }
 
 #if USE_APPLY_GAIN_LUT
@@ -1039,14 +1065,12 @@
           size_t pixel_idx = x + y * width;
 
           switch (output_format) {
-            case ULTRAHDR_OUTPUT_HDR_LINEAR:
-            {
+            case ULTRAHDR_OUTPUT_HDR_LINEAR: {
               uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
               reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
               break;
             }
-            case ULTRAHDR_OUTPUT_HDR_HLG:
-            {
+            case ULTRAHDR_OUTPUT_HDR_HLG: {
 #if USE_HLG_OETF_LUT
               ColorTransformFn hdrOetf = hlgOetfLUT;
 #else
@@ -1057,9 +1081,8 @@
               reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
               break;
             }
-            case ULTRAHDR_OUTPUT_HDR_PQ:
-            {
-#if USE_HLG_OETF_LUT
+            case ULTRAHDR_OUTPUT_HDR_PQ: {
+#if USE_PQ_OETF_LUT
               ColorTransformFn hdrOetf = pqOetfLUT;
 #else
               ColorTransformFn hdrOetf = pqOetf;
@@ -1069,8 +1092,8 @@
               reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
               break;
             }
-            default:
-            {}
+            default: {
+            }
               // Should be impossible to hit after input validation.
           }
         }
@@ -1083,9 +1106,9 @@
   for (int th = 0; th < threads - 1; th++) {
     workers.push_back(std::thread(applyRecMap));
   }
-  const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
-  for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
-    int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
+  const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
+  for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
+    int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
     jobQueue.enqueueJob(rowStart, rowEnd);
     rowStart = rowEnd;
   }
@@ -1095,18 +1118,18 @@
   return NO_ERROR;
 }
 
-status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
-                                              jr_compressed_ptr primary_image,
-                                              jr_compressed_ptr gain_map) {
-  if (compressed_jpegr_image == nullptr) {
+status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
+                                              jr_compressed_ptr primary_jpg_image_ptr,
+                                              jr_compressed_ptr gainmap_jpg_image_ptr) {
+  if (jpegr_image_ptr == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   MessageHandler msg_handler;
   std::shared_ptr<DataSegment> seg =
-                  DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
-                                      static_cast<const uint8_t*>(compressed_jpegr_image->data),
-                                      DataSegment::BufferDispositionPolicy::kDontDelete);
+          DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
+                              static_cast<const uint8_t*>(jpegr_image_ptr->data),
+                              DataSegment::BufferDispositionPolicy::kDontDelete);
   DataSegmentDataSource data_source(seg);
   JpegInfoBuilder jpeg_info_builder;
   jpeg_info_builder.SetImageLimit(2);
@@ -1125,20 +1148,20 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  if (primary_image != nullptr) {
-    primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
-                                               image_ranges[0].GetBegin();
-    primary_image->length = image_ranges[0].GetLength();
+  if (primary_jpg_image_ptr != nullptr) {
+    primary_jpg_image_ptr->data =
+            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
+    primary_jpg_image_ptr->length = image_ranges[0].GetLength();
   }
 
   if (image_ranges.size() == 1) {
     return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
   }
 
-  if (gain_map != nullptr) {
-    gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
-                                              image_ranges[1].GetBegin();
-    gain_map->length = image_ranges[1].GetLength();
+  if (gainmap_jpg_image_ptr != nullptr) {
+    gainmap_jpg_image_ptr->data =
+            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
+    gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
   }
 
   // TODO: choose primary image and gain map image carefully
@@ -1153,7 +1176,8 @@
 // JPEG/R structure:
 // SOI (ff d8)
 //
-// (Optional, only if EXIF package is from outside)
+// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
+// in the JPEG input (Encode API-2, API-3, API-4))
 // APP1 (ff e1)
 // 2 bytes of length (2 + length of exif package)
 // EXIF package (this includes the first two bytes representing the package length)
@@ -1167,7 +1191,7 @@
 // 2 bytes of length
 // MPF
 //
-// (Required) primary image (without the first two bytes (SOI), may have other packages)
+// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
 //
 // SOI (ff d8)
 //
@@ -1183,63 +1207,83 @@
 // Exif 2.2 spec for EXIF marker
 // Adobe XMP spec part 3 for XMP marker
 // ICC v4.3 spec for ICC
-status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
-                              jr_compressed_ptr compressed_gain_map,
-                              jr_exif_ptr exif,
-                              void* icc, size_t icc_size,
-                              ultrahdr_metadata_ptr metadata,
+status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
+                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
+                              void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
                               jr_compressed_ptr dest) {
-  if (compressed_jpeg_image == nullptr
-   || compressed_gain_map == nullptr
-   || metadata == nullptr
-   || dest == nullptr) {
+  if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
+      dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   if (metadata->version.compare("1.0")) {
     ALOGE("received bad value for version: %s", metadata->version.c_str());
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
   if (metadata->maxContentBoost < metadata->minContentBoost) {
     ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
-           metadata->maxContentBoost);
+          metadata->maxContentBoost);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
   if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
     ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
-           metadata->hdrCapacityMax);
+          metadata->hdrCapacityMax);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
   if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
-    ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr,
-           metadata->offsetHdr);
+    ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-
   if (metadata->gamma <= 0.0f) {
     ALOGE("received bad value for gamma %f", metadata->gamma);
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
   const string nameSpace = "http://ns.adobe.com/xap/1.0/";
-  const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
+  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
 
   // calculate secondary image length first, because the length will be written into the primary
   // image xmp
   const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
   const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
-                                 + nameSpaceLength /* 29 bytes length of name space including \0 */
-                                 + xmp_secondary.size(); /* length of xmp packet */
+          + nameSpaceLength          /* 29 bytes length of name space including \0 */
+          + xmp_secondary.size();    /* length of xmp packet */
   const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
-                                 + xmp_secondary_length
-                                 + compressed_gain_map->length;
+          + xmp_secondary_length + gainmap_jpg_image_ptr->length;
   // primary image
   const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
   // same as primary
   const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
 
+  // Check if EXIF package presents in the JPEG input.
+  // If so, extract and remove the EXIF package.
+  JpegDecoderHelper decoder;
+  if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+  jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
+  jpegr_compressed_struct new_jpg_image = {.data = nullptr,
+                                           .length = 0,
+                                           .maxLength = 0,
+                                           .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+  std::unique_ptr<uint8_t[]> dest_data;
+  if (decoder.getEXIFPos() >= 0) {
+    if (pExif != nullptr) {
+      ALOGE("received EXIF from outside while the primary image already contains EXIF");
+      return ERROR_JPEGR_INVALID_INPUT_TYPE;
+    }
+    copyJpegWithoutExif(&new_jpg_image,
+                        primary_jpg_image_ptr,
+                        decoder.getEXIFPos(),
+                        decoder.getEXIFSize());
+    dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
+    exif_from_jpg.data = decoder.getEXIFPtr();
+    exif_from_jpg.length = decoder.getEXIFSize();
+    pExif = &exif_from_jpg;
+  }
+
+  jr_compressed_ptr final_primary_jpg_image_ptr =
+          new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
+
   int pos = 0;
   // Begin primary image
   // Write SOI
@@ -1247,15 +1291,15 @@
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
 
   // Write EXIF
-  if (exif != nullptr) {
-    const int length = 2 + exif->length;
+  if (pExif != nullptr) {
+    const int length = 2 + pExif->length;
     const uint8_t lengthH = ((length >> 8) & 0xff);
     const uint8_t lengthL = (length & 0xff);
     JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
     JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
+    JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
   }
 
   // Prepare and write XMP
@@ -1272,42 +1316,40 @@
   }
 
   // Write ICC
-  if (icc != nullptr && icc_size > 0) {
-      const int length = icc_size + 2;
-      const uint8_t lengthH = ((length >> 8) & 0xff);
-      const uint8_t lengthL = (length & 0xff);
-      JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-      JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-      JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-      JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-      JPEGR_CHECK(Write(dest, icc, icc_size, pos));
+  if (pIcc != nullptr && icc_size > 0) {
+    const int length = icc_size + 2;
+    const uint8_t lengthH = ((length >> 8) & 0xff);
+    const uint8_t lengthL = (length & 0xff);
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+    JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
   }
 
   // Prepare and write MPF
   {
-      const int length = 2 + calculateMpfSize();
-      const uint8_t lengthH = ((length >> 8) & 0xff);
-      const uint8_t lengthL = (length & 0xff);
-      int primary_image_size = pos + length + compressed_jpeg_image->length;
-      // between APP2 + package size + signature
-      // ff e2 00 58 4d 50 46 00
-      // 2 + 2 + 4 = 8 (bytes)
-      // and ff d8 sign of the secondary image
-      int secondary_image_offset = primary_image_size - pos - 8;
-      sp<DataStruct> mpf = generateMpf(primary_image_size,
-                                       0, /* primary_image_offset */
-                                       secondary_image_size,
-                                       secondary_image_offset);
-      JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-      JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-      JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-      JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-      JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
+    const int length = 2 + calculateMpfSize();
+    const uint8_t lengthH = ((length >> 8) & 0xff);
+    const uint8_t lengthL = (length & 0xff);
+    int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
+    // between APP2 + package size + signature
+    // ff e2 00 58 4d 50 46 00
+    // 2 + 2 + 4 = 8 (bytes)
+    // and ff d8 sign of the secondary image
+    int secondary_image_offset = primary_image_size - pos - 8;
+    sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
+                                     secondary_image_size, secondary_image_offset);
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+    JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
   }
 
   // Write primary image
-  JPEGR_CHECK(Write(dest,
-      (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+  JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
+                    final_primary_jpg_image_ptr->length - 2, pos));
   // Finish primary image
 
   // Begin secondary image (gain map)
@@ -1329,8 +1371,8 @@
   }
 
   // Write secondary image
-  JPEGR_CHECK(Write(dest,
-        (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
+  JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
+                    gainmap_jpg_image_ptr->length - 2, pos));
 
   // Set back length
   dest->length = pos;
@@ -1343,62 +1385,52 @@
   if (src == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
-  size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
-
-  uint16_t* src_chroma_data;
-  size_t src_chroma_stride;
-  if (src->chroma_data == nullptr) {
-     src_chroma_stride = src_luma_stride;
-     src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
-  } else {
-     src_chroma_stride = src->chroma_stride;
-     src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+  if (src->width != dest->width || src->height != dest->height) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
-  dest->width = src->width;
-  dest->height = src->height;
-
-  size_t dest_luma_pixel_count = dest->width * dest->height;
-
+  uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
+  uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
   for (size_t y = 0; y < src->height; ++y) {
+    uint16_t* src_y_row = src_y_data + y * src->luma_stride;
+    uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
     for (size_t x = 0; x < src->width; ++x) {
-      size_t src_y_idx = y * src_luma_stride + x;
-      size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
-      size_t src_v_idx = src_u_idx + 1;
-
-      uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
-      uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
-      uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
-
-      size_t dest_y_idx = x + y * dest->width;
-      size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
-
-      uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
-      uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
-      uint8_t* v = &reinterpret_cast<uint8_t*>(
-              dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
-
-      *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
-      *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
-      *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+      uint16_t y_uint = src_y_row[x] >> 6;
+      dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
+    }
+    if (dest->width != dest->luma_stride) {
+      memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
     }
   }
-
+  uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+  uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
+  size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
+  uint8_t* dst_v_data = dst_u_data + dst_v_offset;
+  for (size_t y = 0; y < src->height / 2; ++y) {
+    uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
+    uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
+    uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
+    for (size_t x = 0; x < src->width / 2; ++x) {
+      uint16_t u_uint = src_uv_row[x << 1] >> 6;
+      uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
+      dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
+      dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+    }
+    if (dest->width / 2 != dest->chroma_stride) {
+      memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
+      memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
+    }
+  }
   dest->colorGamut = src->colorGamut;
-
   return NO_ERROR;
 }
 
-status_t JpegR::convertYuv(jr_uncompressed_ptr image,
-                           ultrahdr_color_gamut src_encoding,
+status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
                            ultrahdr_color_gamut dest_encoding) {
   if (image == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
-  if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED
-   || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+  if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+      dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
     return ERROR_JPEGR_INVALID_COLORGAMUT;
   }
 
diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
index 5944130..bda804a 100644
--- a/libs/ultrahdr/tests/Android.bp
+++ b/libs/ultrahdr/tests/Android.bp
@@ -22,12 +22,14 @@
 }
 
 cc_test {
-    name: "libultrahdr_test",
+    name: "ultrahdr_unit_test",
     test_suites: ["device-tests"],
     srcs: [
         "gainmapmath_test.cpp",
         "icchelper_test.cpp",
         "jpegr_test.cpp",
+        "jpegencoderhelper_test.cpp",
+        "jpegdecoderhelper_test.cpp",
     ],
     shared_libs: [
         "libimage_io",
@@ -42,38 +44,7 @@
         "libultrahdr",
         "libutils",
     ],
-}
-
-cc_test {
-    name: "libjpegencoderhelper_test",
-    test_suites: ["device-tests"],
-    srcs: [
-        "jpegencoderhelper_test.cpp",
-    ],
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-    ],
-    static_libs: [
-        "libgtest",
-        "libjpegencoder",
-    ],
-}
-
-cc_test {
-    name: "libjpegdecoderhelper_test",
-    test_suites: ["device-tests"],
-    srcs: [
-        "jpegdecoderhelper_test.cpp",
-    ],
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-    ],
-    static_libs: [
-        "libgtest",
-        "libjpegdecoder",
-        "libultrahdr",
-        "libutils",
+    data: [
+        "./data/*.*",
     ],
 }
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
new file mode 100644
index 0000000..1754a5c
--- /dev/null
+++ b/libs/ultrahdr/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the"License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an"AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Unit test configuration for ultrahdr_unit_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
+        <option name="push" value="data/*->/data/local/tmp/" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="ultrahdr_unit_test" />
+    </test>
+</configuration>
diff --git a/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010
deleted file mode 100644
index e7a5dc8..0000000
--- a/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
index af90365..7c2d076 100644
--- a/libs/ultrahdr/tests/gainmapmath_test.cpp
+++ b/libs/ultrahdr/tests/gainmapmath_test.cpp
@@ -120,7 +120,7 @@
       0xB0, 0xB1,
       0xB2, 0xB3,
     };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 };
+    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 };
   }
 
   Color (*Yuv420Colors())[4] {
@@ -153,7 +153,7 @@
       0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
       0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
     };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 };
+    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 };
   }
 
   Color (*P010Colors())[4] {
@@ -636,6 +636,9 @@
     memcpy(out_buf.get(), input.data, out_buf_size);
     jpegr_uncompressed_struct output = Yuv420Image();
     output.data = out_buf.get();
+    output.chroma_data = out_buf.get() + input.width * input.height;
+    output.luma_stride = input.width;
+    output.chroma_stride = input.width / 2;
 
     transformYuv420(&output, 1, 1, transform);
 
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
index e2da01c..af0d59e 100644
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <ultrahdr/icc.h>
 #include <gtest/gtest.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegdecoderhelper.h>
 #include <utils/Log.h>
 
 #include <fcntl.h>
@@ -24,13 +24,13 @@
 namespace android::ultrahdr {
 
 // No ICC or EXIF
-#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
 #define YUV_IMAGE_SIZE 20193
 // Has ICC and EXIF
-#define YUV_ICC_IMAGE "/sdcard/Documents/minnie-320x240-yuv-icc.jpg"
+#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
 #define YUV_ICC_IMAGE_SIZE 34266
 // No ICC or EXIF
-#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
+#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
 #define GREY_IMAGE_SIZE 20193
 
 #define IMAGE_WIDTH 320
@@ -44,6 +44,7 @@
     };
     JpegDecoderHelperTest();
     ~JpegDecoderHelperTest();
+
 protected:
     virtual void SetUp();
     virtual void TearDown();
@@ -127,8 +128,8 @@
     std::vector<uint8_t> icc, exif;
 
     JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size,
-                                                     &width, &height, &icc, &exif));
+    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width,
+                                                     &height, &icc, &exif));
 
     EXPECT_EQ(width, IMAGE_WIDTH);
     EXPECT_EQ(height, IMAGE_HEIGHT);
@@ -149,8 +150,7 @@
     EXPECT_GT(icc.size(), 0);
     EXPECT_GT(exif.size(), 0);
 
-    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()),
-              ULTRAHDR_COLORGAMUT_BT709);
+    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709);
 }
 
-}  // namespace android::ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
index f0e1fa4..af54eb2 100644
--- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
@@ -22,13 +22,13 @@
 
 namespace android::ultrahdr {
 
-#define ALIGNED_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
 #define ALIGNED_IMAGE_WIDTH 320
 #define ALIGNED_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
 #define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
 #define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
-#define UNALIGNED_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12"
 #define UNALIGNED_IMAGE_WIDTH 318
 #define UNALIGNED_IMAGE_HEIGHT 240
 #define JPEG_QUALITY 90
@@ -42,6 +42,7 @@
     };
     JpegEncoderHelperTest();
     ~JpegEncoderHelperTest();
+
 protected:
     virtual void SetUp();
     virtual void TearDown();
@@ -103,24 +104,32 @@
 
 TEST_F(JpegEncoderHelperTest, encodeAlignedImage) {
     JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), mAlignedImage.width,
-                                      mAlignedImage.height, JPEG_QUALITY, NULL, 0));
+    EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(),
+                                      mAlignedImage.buffer.get() +
+                                              mAlignedImage.width * mAlignedImage.height,
+                                      mAlignedImage.width, mAlignedImage.height,
+                                      mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY,
+                                      NULL, 0));
     ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
 }
 
 TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
     JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(), mUnalignedImage.width,
-                                      mUnalignedImage.height, JPEG_QUALITY, NULL, 0));
+    EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(),
+                                      mUnalignedImage.buffer.get() +
+                                              mUnalignedImage.width * mUnalignedImage.height,
+                                      mUnalignedImage.width, mUnalignedImage.height,
+                                      mUnalignedImage.width, mUnalignedImage.width / 2,
+                                      JPEG_QUALITY, NULL, 0));
     ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
 }
 
 TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) {
     JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
-                                         mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
+    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr,
+                                      mSingleChannelImage.width, mSingleChannelImage.height,
+                                      mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0));
     ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
 }
 
-}  // namespace android::ultrahdr
-
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
index 41d55ec..5fa758e 100644
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -14,810 +14,1232 @@
  * limitations under the License.
  */
 
+#include <sys/time.h>
+#include <fstream>
+#include <iostream>
+
+#include <ultrahdr/gainmapmath.h>
 #include <ultrahdr/jpegr.h>
 #include <ultrahdr/jpegrutils.h>
-#include <ultrahdr/gainmapmath.h>
-#include <fcntl.h>
-#include <fstream>
+
 #include <gtest/gtest.h>
-#include <sys/time.h>
 #include <utils/Log.h>
 
-#define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
-#define RAW_P010_IMAGE_WITH_STRIDE "/sdcard/Documents/raw_p010_image_with_stride.p010"
-#define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420"
-#define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
-#define TEST_IMAGE_WIDTH 1280
-#define TEST_IMAGE_HEIGHT 720
-#define TEST_IMAGE_STRIDE 1288
-#define DEFAULT_JPEG_QUALITY 90
-
-#define SAVE_ENCODING_RESULT true
-#define SAVE_DECODING_RESULT true
-#define SAVE_INPUT_RGBA true
+//#define DUMP_OUTPUT
 
 namespace android::ultrahdr {
 
-struct Timer {
-  struct timeval StartingTime;
-  struct timeval EndingTime;
-  struct timeval ElapsedMicroseconds;
-};
+// resources used by unit tests
+const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
+const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
+const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
+const int kImageWidth = 1280;
+const int kImageHeight = 720;
+const int kQuality = 90;
 
-void timerStart(Timer *t) {
-  gettimeofday(&t->StartingTime, nullptr);
-}
+// Wrapper to describe the input type
+typedef enum {
+  YCbCr_p010 = 0,
+  YCbCr_420 = 1,
+} UhdrInputFormat;
 
-void timerStop(Timer *t) {
-  gettimeofday(&t->EndingTime, nullptr);
-}
-
-int64_t elapsedTime(Timer *t) {
-  t->ElapsedMicroseconds.tv_sec = t->EndingTime.tv_sec - t->StartingTime.tv_sec;
-  t->ElapsedMicroseconds.tv_usec = t->EndingTime.tv_usec - t->StartingTime.tv_usec;
-  return t->ElapsedMicroseconds.tv_sec * 1000000 + t->ElapsedMicroseconds.tv_usec;
-}
-
-static size_t getFileSize(int fd) {
-  struct stat st;
-  if (fstat(fd, &st) < 0) {
-    ALOGW("%s : fstat failed", __func__);
-    return 0;
-  }
-  return st.st_size; // bytes
-}
-
-static bool loadFile(const char filename[], void*& result, int* fileLength) {
-  int fd = open(filename, O_CLOEXEC);
-  if (fd < 0) {
-    return false;
-  }
-  int length = getFileSize(fd);
-  if (length == 0) {
-    close(fd);
-    return false;
-  }
-  if (fileLength != nullptr) {
-    *fileLength = length;
-  }
-  result = malloc(length);
-  if (read(fd, result, length) != static_cast<ssize_t>(length)) {
-    close(fd);
-    return false;
-  }
-  close(fd);
-  return true;
-}
-
-static bool loadP010Image(const char *filename, jr_uncompressed_ptr img,
-                          bool isUVContiguous) {
-  int fd = open(filename, O_CLOEXEC);
-  if (fd < 0) {
-    return false;
-  }
-  const int bpp = 2;
-  int lumaStride = img->luma_stride == 0 ? img->width : img->luma_stride;
-  int lumaSize = bpp * lumaStride * img->height;
-  int chromaSize = bpp * (img->height / 2) *
-                   (isUVContiguous ? lumaStride : img->chroma_stride);
-  img->data = malloc(lumaSize + (isUVContiguous ? chromaSize : 0));
-  if (img->data == nullptr) {
-    ALOGE("loadP010Image(): failed to allocate memory for luma data.");
-    return false;
-  }
-  uint8_t *mem = static_cast<uint8_t *>(img->data);
-  for (int i = 0; i < img->height; i++) {
-    if (read(fd, mem, img->width * bpp) != img->width * bpp) {
-      close(fd);
-      return false;
-    }
-    mem += lumaStride * bpp;
-  }
-  int chromaStride = lumaStride;
-  if (!isUVContiguous) {
-    img->chroma_data = malloc(chromaSize);
-    if (img->chroma_data == nullptr) {
-      ALOGE("loadP010Image(): failed to allocate memory for chroma data.");
-      return false;
-    }
-    mem = static_cast<uint8_t *>(img->chroma_data);
-    chromaStride = img->chroma_stride;
-  }
-  for (int i = 0; i < img->height / 2; i++) {
-    if (read(fd, mem, img->width * bpp) != img->width * bpp) {
-      close(fd);
-      return false;
-    }
-    mem += chromaStride * bpp;
-  }
-  close(fd);
-  return true;
-}
-
-class JpegRTest : public testing::Test {
+/**
+ * Wrapper class for raw resource
+ * Sample usage:
+ *   UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010);
+ *   rawImg.setImageColorGamut(colorGamut));
+ *   rawImg.setImageStride(strideLuma, strideChroma); // optional
+ *   rawImg.setChromaMode(false); // optional
+ *   rawImg.allocateMemory();
+ *   rawImg.loadRawResource(kYCbCrP010FileName);
+ */
+class UhdrUnCompressedStructWrapper {
 public:
-  JpegRTest();
-  ~JpegRTest();
+  UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format);
+  ~UhdrUnCompressedStructWrapper() = default;
 
-protected:
-  virtual void SetUp();
-  virtual void TearDown();
+  bool setChromaMode(bool isChromaContiguous);
+  bool setImageStride(int lumaStride, int chromaStride);
+  bool setImageColorGamut(ultrahdr_color_gamut colorGamut);
+  bool allocateMemory();
+  bool loadRawResource(const char* fileName);
+  jr_uncompressed_ptr getImageHandle();
 
-  struct jpegr_uncompressed_struct mRawP010Image{};
-  struct jpegr_uncompressed_struct mRawP010ImageWithStride{};
-  struct jpegr_uncompressed_struct mRawP010ImageWithChromaData{};
-  struct jpegr_uncompressed_struct mRawYuv420Image{};
-  struct jpegr_compressed_struct mJpegImage{};
-};
-
-JpegRTest::JpegRTest() {}
-JpegRTest::~JpegRTest() {}
-
-void JpegRTest::SetUp() {}
-void JpegRTest::TearDown() {
-  free(mRawP010Image.data);
-  free(mRawP010Image.chroma_data);
-  free(mRawP010ImageWithStride.data);
-  free(mRawP010ImageWithStride.chroma_data);
-  free(mRawP010ImageWithChromaData.data);
-  free(mRawP010ImageWithChromaData.chroma_data);
-  free(mRawYuv420Image.data);
-  free(mJpegImage.data);
-}
-
-class JpegRBenchmark : public JpegR {
-public:
- void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
-                               ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
- void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                            ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
 private:
- const int kProfileCount = 10;
+  std::unique_ptr<uint8_t[]> mLumaData;
+  std::unique_ptr<uint8_t[]> mChromaData;
+  jpegr_uncompressed_struct mImg;
+  UhdrInputFormat mFormat;
+  bool mIsChromaContiguous;
 };
 
-void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
-                                              jr_uncompressed_ptr p010Image,
-                                              ultrahdr_metadata_ptr metadata,
-                                              jr_uncompressed_ptr map) {
-  ASSERT_EQ(yuv420Image->width, p010Image->width);
-  ASSERT_EQ(yuv420Image->height, p010Image->height);
+/**
+ * Wrapper class for compressed resource
+ * Sample usage:
+ *   UhdrCompressedStructWrapper jpgImg(width, height);
+ *   rawImg.allocateMemory();
+ */
+class UhdrCompressedStructWrapper {
+public:
+  UhdrCompressedStructWrapper(uint32_t width, uint32_t height);
+  ~UhdrCompressedStructWrapper() = default;
 
-  Timer genRecMapTime;
+  bool allocateMemory();
+  jr_compressed_ptr getImageHandle();
 
-  timerStart(&genRecMapTime);
-  for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, generateGainMap(
-          yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, metadata, map));
-      if (i != kProfileCount - 1) delete[] static_cast<uint8_t *>(map->data);
+private:
+  std::unique_ptr<uint8_t[]> mData;
+  jpegr_compressed_struct mImg{};
+  uint32_t mWidth;
+  uint32_t mHeight;
+};
+
+UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height,
+                                                             UhdrInputFormat format) {
+  mImg.data = nullptr;
+  mImg.width = width;
+  mImg.height = height;
+  mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+  mImg.chroma_data = nullptr;
+  mImg.luma_stride = 0;
+  mImg.chroma_stride = 0;
+  mFormat = format;
+  mIsChromaContiguous = true;
+}
+
+bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) {
+  if (mLumaData.get() != nullptr) {
+    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
+    return false;
   }
-  timerStop(&genRecMapTime);
-
-  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms",
-        yuv420Image->width, yuv420Image->height,
-        elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f));
-
+  mIsChromaContiguous = isChromaContiguous;
+  return true;
 }
 
-void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image,
-                                           jr_uncompressed_ptr map,
-                                           ultrahdr_metadata_ptr metadata,
-                                           jr_uncompressed_ptr dest) {
-  Timer applyRecMapTime;
-
-  timerStart(&applyRecMapTime);
-  for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
-                                 metadata->maxContentBoost /* displayBoost */, dest));
+bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) {
+  if (mLumaData.get() != nullptr) {
+    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
+    return false;
   }
-  timerStop(&applyRecMapTime);
-
-  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms",
-        yuv420Image->width, yuv420Image->height,
-        elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f));
+  if (lumaStride != 0) {
+    if (lumaStride < mImg.width) {
+      std::cerr << "Bad luma stride received" << std::endl;
+      return false;
+    }
+    mImg.luma_stride = lumaStride;
+  }
+  if (chromaStride != 0) {
+    if (mFormat == YCbCr_p010 && chromaStride < mImg.width) {
+      std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl;
+      return false;
+    }
+    if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) {
+      std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl;
+      return false;
+    }
+    mImg.chroma_stride = chromaStride;
+  }
+  return true;
 }
 
-TEST_F(JpegRTest, build) {
-  // Force all of the gain map lib to be linked by calling all public functions.
-  JpegR jpegRCodec;
-  jpegRCodec.encodeJPEGR(nullptr, static_cast<ultrahdr_transfer_function>(0), nullptr, 0, nullptr);
-  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0),
-                         nullptr, 0, nullptr);
-  jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0),
-                         nullptr);
-  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0), nullptr);
-  jpegRCodec.decodeJPEGR(nullptr, nullptr);
+bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) {
+  if (mLumaData.get() != nullptr) {
+    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
+    return false;
+  }
+  mImg.colorGamut = colorGamut;
+  return true;
 }
 
-/* Test Encode API-0 invalid arguments */
-TEST_F(JpegRTest, encodeAPI0ForInvalidArgs) {
-  int ret;
+bool UhdrUnCompressedStructWrapper::allocateMemory() {
+  if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) ||
+      (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) {
+    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+    return false;
+  }
+  int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
+  int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1);
+  int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1);
+  if (mIsChromaContiguous) {
+    chromaSize *= lumaStride;
+  } else {
+    if (mImg.chroma_stride == 0) {
+      std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+      return false;
+    }
+    if (mFormat == YCbCr_p010) {
+      chromaSize *= mImg.chroma_stride;
+    } else {
+      chromaSize *= (mImg.chroma_stride * 2);
+    }
+  }
+  if (mIsChromaContiguous) {
+    mLumaData = std::make_unique<uint8_t[]>(lumaSize + chromaSize);
+    mImg.data = mLumaData.get();
+    mImg.chroma_data = nullptr;
+  } else {
+    mLumaData = std::make_unique<uint8_t[]>(lumaSize);
+    mImg.data = mLumaData.get();
+    mChromaData = std::make_unique<uint8_t[]>(chromaSize);
+    mImg.chroma_data = mChromaData.get();
+  }
+  return true;
+}
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = 16 * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
+bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) {
+  if (!mImg.data) {
+    std::cerr << "memory is not allocated, read not possible" << std::endl;
+    return false;
+  }
+  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
+  if (ifd.good()) {
+    int bpp = mFormat == YCbCr_p010 ? 2 : 1;
+    int size = ifd.tellg();
+    int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling
+    if (size < length) {
+      std::cerr << "requested to read " << length << " bytes from file : " << fileName
+                << ", file contains only " << length << " bytes" << std::endl;
+      return false;
+    }
+    ifd.seekg(0, std::ios::beg);
+    int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
+    char* mem = static_cast<char*>(mImg.data);
+    for (int i = 0; i < mImg.height; i++) {
+      ifd.read(mem, mImg.width * bpp);
+      mem += lumaStride * bpp;
+    }
+    if (!mIsChromaContiguous) {
+      mem = static_cast<char*>(mImg.chroma_data);
+    }
+    int chromaStride;
+    if (mIsChromaContiguous) {
+      chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2;
+    } else {
+      if (mFormat == YCbCr_p010) {
+        chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride;
+      } else {
+        chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride;
+      }
+    }
+    if (mFormat == YCbCr_p010) {
+      for (int i = 0; i < mImg.height / 2; i++) {
+        ifd.read(mem, mImg.width * 2);
+        mem += chromaStride * 2;
+      }
+    } else {
+      for (int i = 0; i < mImg.height / 2; i++) {
+        ifd.read(mem, (mImg.width / 2));
+        mem += chromaStride;
+      }
+      for (int i = 0; i < mImg.height / 2; i++) {
+        ifd.read(mem, (mImg.width / 2));
+        mem += chromaStride;
+      }
+    }
+    return true;
+  }
+  std::cerr << "unable to open file : " << fileName << std::endl;
+  return false;
+}
 
-  JpegR jpegRCodec;
+jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() {
+  return &mImg;
+}
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  mRawP010ImageWithStride.data = malloc(16);
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) {
+  mWidth = width;
+  mHeight = height;
+}
 
-  // test quality factor
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      -1, nullptr)) << "fail, API allows bad jpeg quality factor";
+bool UhdrCompressedStructWrapper::allocateMemory() {
+  if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) {
+    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+    return false;
+  }
+  int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2));
+  mData = std::make_unique<uint8_t[]>(maxLength);
+  mImg.data = mData.get();
+  mImg.length = 0;
+  mImg.maxLength = maxLength;
+  return true;
+}
 
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      101, nullptr)) << "fail, API allows bad jpeg quality factor";
+jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() {
+  return &mImg;
+}
 
-  // test hdr transfer function
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+static bool writeFile(const char* filename, void*& result, int length) {
+  std::ofstream ofd(filename, std::ios::binary);
+  if (ofd.is_open()) {
+    ofd.write(static_cast<char*>(result), length);
+    return true;
+  }
+  std::cerr << "unable to write to file : " << filename << std::endl;
+  return false;
+}
 
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride,
-      static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+static bool readFile(const char* fileName, void*& result, int maxLength, int& length) {
+  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
+  if (ifd.good()) {
+    length = ifd.tellg();
+    if (length > maxLength) {
+      std::cerr << "not enough space to read file" << std::endl;
+      return false;
+    }
+    ifd.seekg(0, std::ios::beg);
+    ifd.read(static_cast<char*>(result), length);
+    return true;
+  }
+  std::cerr << "unable to read file : " << fileName << std::endl;
+  return false;
+}
 
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride,
-      static_cast<ultrahdr_transfer_function>(-10),
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
+  std::vector<uint8_t> iccData(0);
+  std::vector<uint8_t> exifData(0);
+  jpegr_info_struct info{0, 0, &iccData, &exifData};
+  JpegR jpegHdr;
+  ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info));
+  ASSERT_EQ(kImageWidth, info.width);
+  ASSERT_EQ(kImageHeight, info.height);
+  size_t outSize = info.width * info.height * 8;
+  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
+  jpegr_uncompressed_struct destImage{};
+  destImage.data = data.get();
+  ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage));
+  ASSERT_EQ(kImageWidth, destImage.width);
+  ASSERT_EQ(kImageHeight, destImage.height);
+#ifdef DUMP_OUTPUT
+  if (!writeFile(outFileName, destImage.data, outSize)) {
+    std::cerr << "unable to write output file" << std::endl;
+  }
+#endif
+}
+
+// ============================================================================
+// Unit Tests
+// ============================================================================
+
+// Test Encode API-0 invalid arguments
+TEST(JpegRTest, EncodeAPI0WithInvalidArgs) {
+  JpegR uHdrLib;
+
+  UhdrCompressedStructWrapper jpgImg(16, 16);
+  ASSERT_TRUE(jpgImg.allocateMemory());
+
+  // test quality factor and transfer function
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), -1, nullptr),
+              OK)
+            << "fail, API allows bad jpeg quality factor";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), 101, nullptr),
+              OK)
+            << "fail, API allows bad jpeg quality factor";
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(
+                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(-10),
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+  }
 
   // test dest
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
+                                  nullptr),
+              OK)
+            << "fail, API allows nullptr dest";
+    UhdrCompressedStructWrapper jpgImg2(16, 16);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr dest";
+  }
 
   // test p010 input
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image";
+  {
+    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr p010 image";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr p010 image";
+  }
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
-      ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(
+            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1)));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad p010 color gamut";
+  }
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+  {
+    const int kWidth = 32, kHeight = 32;
+    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    auto rawImgP010 = rawImg.getImageHandle();
 
-  mRawP010ImageWithStride.width = 0;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth - 1;
+    rawImgP010->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = 0;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight - 1;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image height";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride";
+    rawImgP010->width = 0;
+    rawImgP010->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
-  mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = 0;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image height";
 
-  mRawP010ImageWithStride.chroma_data = nullptr;
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad luma stride";
 
-  free(jpegR.data);
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth + 64;
+    rawImgP010->chroma_data = rawImgP010->data;
+    rawImgP010->chroma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad chroma stride";
+  }
 }
 
 /* Test Encode API-1 invalid arguments */
-TEST_F(JpegRTest, encodeAPI1ForInvalidArgs) {
-  int ret;
+TEST(JpegRTest, EncodeAPI1WithInvalidArgs) {
+  JpegR uHdrLib;
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = 16 * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
+  UhdrCompressedStructWrapper jpgImg(16, 16);
+  ASSERT_TRUE(jpgImg.allocateMemory());
 
-  JpegR jpegRCodec;
+  // test quality factor and transfer function
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  mRawP010ImageWithStride.data = malloc(16);
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), -1, nullptr),
+              OK)
+            << "fail, API allows bad jpeg quality factor";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), 101, nullptr),
+              OK)
+            << "fail, API allows bad jpeg quality factor";
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  mRawYuv420Image.data = malloc(16);
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
-  // test quality factor
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, -1, nullptr)) << "fail, API allows bad jpeg quality factor";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, 101, nullptr)) << "fail, API allows bad jpeg quality factor";
-
-  // test hdr transfer function
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image,
-      ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, DEFAULT_JPEG_QUALITY,
-      nullptr)) << "fail, API allows bad hdr transfer function";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image,
-      static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image,
-      static_cast<ultrahdr_transfer_function>(-10),
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(
+                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(-10),
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+  }
 
   // test dest
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      nullptr, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
+                                  nullptr),
+              OK)
+            << "fail, API allows nullptr dest";
+    UhdrCompressedStructWrapper jpgImg2(16, 16);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr dest";
+  }
 
   // test p010 input
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      nullptr, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image";
+  {
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr p010 image";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr p010 image";
+  }
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
-      ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+  {
+    const int kWidth = 32, kHeight = 32;
+    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    auto rawImgP010 = rawImg.getImageHandle();
+    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    auto rawImg420 = rawImg2.getImageHandle();
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut =
+            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = 0;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth - 1;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = 0;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight - 1;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image height";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride";
+    rawImgP010->width = 0;
+    rawImgP010->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
-  mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = 0;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image height";
+
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad luma stride";
+
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth + 64;
+    rawImgP010->chroma_data = rawImgP010->data;
+    rawImgP010->chroma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad chroma stride";
+  }
 
   // test 420 input
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.chroma_data = nullptr;
-  mRawP010ImageWithStride.chroma_stride = 0;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr for 420 image";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr 420 image";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image width";
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows nullptr 420 image";
+  }
+  {
+    const int kWidth = 32, kHeight = 32;
+    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    auto rawImgP010 = rawImg.getImageHandle();
+    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    auto rawImg420 = rawImg2.getImageHandle();
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image height";
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad 420 color gamut";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride for 420";
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->colorGamut =
+            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad 420 color gamut";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.luma_stride = 0;
-  mRawYuv420Image.chroma_data = mRawYuv420Image.data;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows chroma pointer for 420";
+    rawImg420->width = kWidth - 1;
+    rawImg420->height = kHeight;
+    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image width for 420";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.luma_stride = 0;
-  mRawYuv420Image.chroma_data = nullptr;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut";
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight - 1;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image height for 420";
 
-  mRawYuv420Image.colorGamut = static_cast<ultrahdr_color_gamut>(
-      ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut";
+    rawImg420->width = 0;
+    rawImg420->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image width for 420";
 
-  free(jpegR.data);
+    rawImg420->width = kWidth;
+    rawImg420->height = 0;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad image height for 420";
+
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->luma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad luma stride for 420";
+
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->luma_stride = 0;
+    rawImg420->chroma_data = rawImgP010->data;
+    rawImg420->chroma_stride = kWidth / 2 - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle(), kQuality, nullptr),
+              OK)
+            << "fail, API allows bad chroma stride for 420";
+  }
 }
 
 /* Test Encode API-2 invalid arguments */
-TEST_F(JpegRTest, encodeAPI2ForInvalidArgs) {
-  int ret;
+TEST(JpegRTest, EncodeAPI2WithInvalidArgs) {
+  JpegR uHdrLib;
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = 16 * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
+  UhdrCompressedStructWrapper jpgImg(16, 16);
+  ASSERT_TRUE(jpgImg.allocateMemory());
 
-  JpegR jpegRCodec;
+  // test quality factor and transfer function
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  mRawP010ImageWithStride.data = malloc(16);
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  // we are not really compressing anything so lets keep allocs to a minimum
-  mRawYuv420Image.data = malloc(16);
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
-  // test hdr transfer function
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-      &jpegR)) << "fail, API allows bad hdr transfer function";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-      &jpegR)) << "fail, API allows bad hdr transfer function";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      static_cast<ultrahdr_transfer_function>(-10),
-      &jpegR)) << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(
+                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(-10),
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+  }
 
   // test dest
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr)) << "fail, API allows nullptr dest";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
+              OK)
+            << "fail, API allows nullptr dest";
+    UhdrCompressedStructWrapper jpgImg2(16, 16);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr dest";
+  }
+
+  // test compressed image
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr for compressed image";
+    UhdrCompressedStructWrapper jpgImg2(16, 16);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr for compressed image";
+  }
 
   // test p010 input
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      nullptr, &mRawYuv420Image, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows nullptr p010 image";
+  {
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr p010 image";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad p010 color gamut";
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr p010 image";
+  }
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
-      ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad p010 color gamut";
+  {
+    const int kWidth = 32, kHeight = 32;
+    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    auto rawImgP010 = rawImg.getImageHandle();
+    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    auto rawImg420 = rawImg2.getImageHandle();
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut =
+            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = 0;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth - 1;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = 0;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight - 1;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image height";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad luma stride";
+    rawImgP010->width = 0;
+    rawImgP010->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
-  mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad chroma stride";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = 0;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image height";
+
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad luma stride";
+
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth + 64;
+    rawImgP010->chroma_data = rawImgP010->data;
+    rawImgP010->chroma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad chroma stride";
+  }
 
   // test 420 input
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.chroma_data = nullptr;
-  mRawP010ImageWithStride.chroma_stride = 0;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, nullptr, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows nullptr for 420 image";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr 420 image";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad 420 image width";
+    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+                                  jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr 420 image";
+  }
+  {
+    const int kWidth = 32, kHeight = 32;
+    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    auto rawImgP010 = rawImg.getImageHandle();
+    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    auto rawImg420 = rawImg2.getImageHandle();
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad 420 image height";
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad 420 color gamut";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad luma stride for 420";
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->colorGamut =
+            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad 420 color gamut";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.luma_stride = 0;
-  mRawYuv420Image.chroma_data = mRawYuv420Image.data;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows chroma pointer for 420";
+    rawImg420->width = kWidth - 1;
+    rawImg420->height = kHeight;
+    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image width for 420";
 
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.luma_stride = 0;
-  mRawYuv420Image.chroma_data = nullptr;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad 420 color gamut";
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight - 1;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image height for 420";
 
-  mRawYuv420Image.colorGamut = static_cast<ultrahdr_color_gamut>(
-      ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad 420 color gamut";
+    rawImg420->width = 0;
+    rawImg420->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image width for 420";
 
-  // bad compressed image
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &mRawYuv420Image, nullptr,
-      ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad 420 color gamut";
+    rawImg420->width = kWidth;
+    rawImg420->height = 0;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image height for 420";
 
-  free(jpegR.data);
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->luma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad luma stride for 420";
+
+    rawImg420->width = kWidth;
+    rawImg420->height = kHeight;
+    rawImg420->luma_stride = 0;
+    rawImg420->chroma_data = rawImgP010->data;
+    rawImg420->chroma_stride = kWidth / 2 - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad chroma stride for 420";
+  }
 }
 
 /* Test Encode API-3 invalid arguments */
-TEST_F(JpegRTest, encodeAPI3ForInvalidArgs) {
-  int ret;
+TEST(JpegRTest, EncodeAPI3WithInvalidArgs) {
+  JpegR uHdrLib;
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = 16 * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
+  UhdrCompressedStructWrapper jpgImg(16, 16);
+  ASSERT_TRUE(jpgImg.allocateMemory());
 
-  JpegR jpegRCodec;
+  // test quality factor and transfer function
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  mRawP010ImageWithStride.data = malloc(16);
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  // test hdr transfer function
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-      &jpegR)) << "fail, API allows bad hdr transfer function";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR,
-      static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-      &jpegR)) << "fail, API allows bad hdr transfer function";
-
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, static_cast<ultrahdr_transfer_function>(-10),
-      &jpegR)) << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(
+                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+                                  static_cast<ultrahdr_transfer_function>(-10),
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad hdr transfer function";
+  }
 
   // test dest
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      nullptr)) << "fail, API allows nullptr dest";
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
+              OK)
+            << "fail, API allows nullptr dest";
+    UhdrCompressedStructWrapper jpgImg2(16, 16);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr dest";
+  }
+
+  // test compressed image
+  {
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr for compressed image";
+    UhdrCompressedStructWrapper jpgImg2(16, 16);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr for compressed image";
+  }
 
   // test p010 input
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      nullptr, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows nullptr p010 image";
+  {
+    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr p010 image";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad p010 color gamut";
+    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows nullptr p010 image";
+  }
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
-      ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad p010 color gamut";
+  {
+    const int kWidth = 32, kHeight = 32;
+    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+    ASSERT_TRUE(rawImg.allocateMemory());
+    auto rawImgP010 = rawImg.getImageHandle();
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut =
+            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad p010 color gamut";
 
-  mRawP010ImageWithStride.width = 0;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad image width";
+    rawImgP010->width = kWidth - 1;
+    rawImgP010->height = kHeight;
+    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = 0;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad image height";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight - 1;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image height";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad luma stride";
+    rawImgP010->width = 0;
+    rawImgP010->height = kHeight;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image width";
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
-  mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad chroma stride";
-  mRawP010ImageWithStride.chroma_data = nullptr;
+    rawImgP010->width = kWidth;
+    rawImgP010->height = 0;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad image height";
 
-  // bad compressed image
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR)) << "fail, API allows bad 420 color gamut";
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad luma stride";
 
-  free(jpegR.data);
+    rawImgP010->width = kWidth;
+    rawImgP010->height = kHeight;
+    rawImgP010->luma_stride = kWidth + 64;
+    rawImgP010->chroma_data = rawImgP010->data;
+    rawImgP010->chroma_stride = kWidth - 2;
+    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg.getImageHandle()),
+              OK)
+            << "fail, API allows bad chroma stride";
+  }
 }
 
 /* Test Encode API-4 invalid arguments */
-TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) {
-  int ret;
-
-  // we are not really compressing anything so lets keep allocs to a minimum
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = 16 * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-
-  JpegR jpegRCodec;
+TEST(JpegRTest, EncodeAPI4WithInvalidArgs) {
+  UhdrCompressedStructWrapper jpgImg(16, 16);
+  ASSERT_TRUE(jpgImg.allocateMemory());
+  UhdrCompressedStructWrapper jpgImg2(16, 16);
+  JpegR uHdrLib;
 
   // test dest
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, &jpegR, nullptr, nullptr)) << "fail, API allows nullptr dest";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr),
+            OK)
+          << "fail, API allows nullptr dest";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr,
+                                jpgImg2.getImageHandle()),
+            OK)
+          << "fail, API allows nullptr dest";
 
   // test primary image
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      nullptr, &jpegR, nullptr, &jpegR)) << "fail, API allows nullptr primary image";
+  ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows nullptr primary image";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows nullptr primary image";
 
   // test gain map
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap image";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows nullptr gain map image";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows nullptr gain map image";
 
   // test metadata
   ultrahdr_metadata_struct good_metadata;
@@ -832,82 +1254,95 @@
 
   ultrahdr_metadata_struct metadata = good_metadata;
   metadata.version = "1.1";
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata version";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata version";
 
   metadata = good_metadata;
   metadata.minContentBoost = 3.0f;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata content boost";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata content boost";
 
   metadata = good_metadata;
   metadata.gamma = -0.1f;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata gamma";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata gamma";
 
   metadata = good_metadata;
   metadata.offsetSdr = -0.1f;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset sdr";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata offset sdr";
 
   metadata = good_metadata;
   metadata.offsetHdr = -0.1f;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset hdr";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata offset hdr";
 
   metadata = good_metadata;
   metadata.hdrCapacityMax = 0.5f;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity max";
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata hdr capacity max";
 
   metadata = good_metadata;
   metadata.hdrCapacityMin = 0.5f;
-  EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
-      &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity min";
-
-  free(jpegR.data);
+  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+                                jpgImg.getImageHandle()),
+            OK)
+          << "fail, API allows bad metadata hdr capacity min";
 }
 
 /* Test Decode API invalid arguments */
-TEST_F(JpegRTest, decodeAPIForInvalidArgs) {
-  int ret;
+TEST(JpegRTest, DecodeAPIWithInvalidArgs) {
+  JpegR uHdrLib;
 
-  // we are not really compressing anything so lets keep allocs to a minimum
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = 16 * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-
-  // we are not really decoding anything so lets keep allocs to a minimum
-  mRawP010Image.data = malloc(16);
-
-  JpegR jpegRCodec;
+  UhdrCompressedStructWrapper jpgImg(16, 16);
+  jpegr_uncompressed_struct destImage{};
+  size_t outSize = 16 * 16 * 8;
+  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
+  destImage.data = data.get();
 
   // test jpegr image
-  EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
-        nullptr, &mRawP010Image)) << "fail, API allows nullptr for jpegr img";
+  ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK)
+          << "fail, API allows nullptr for jpegr img";
+  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
+          << "fail, API allows nullptr for jpegr img";
+  ASSERT_TRUE(jpgImg.allocateMemory());
 
   // test dest image
-  EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
-        &jpegR, nullptr)) << "fail, API allows nullptr for dest";
+  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK)
+          << "fail, API allows nullptr for dest";
+  destImage.data = nullptr;
+  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
+          << "fail, API allows nullptr for dest";
+  destImage.data = data.get();
 
   // test max display boost
-  EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
-        &jpegR, &mRawP010Image, 0.5)) << "fail, API allows invalid max display boost";
+  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK)
+          << "fail, API allows invalid max display boost";
 
   // test output format
-  EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
-        &jpegR, &mRawP010Image, 0.5, nullptr,
-        static_cast<ultrahdr_output_format>(-1))) << "fail, API allows invalid output format";
-
-  EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
-        &jpegR, &mRawP010Image, 0.5, nullptr,
-        static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)))
-        << "fail, API allows invalid output format";
-
-  free(jpegR.data);
+  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
+                                static_cast<ultrahdr_output_format>(-1)),
+            OK)
+          << "fail, API allows invalid output format";
+  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
+                                static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)),
+            OK)
+          << "fail, API allows invalid output format";
 }
 
-TEST_F(JpegRTest, writeXmpThenRead) {
+TEST(JpegRTest, writeXmpThenRead) {
   ultrahdr_metadata_struct metadata_expected;
   metadata_expected.version = "1.0";
   metadata_expected.maxContentBoost = 1.25f;
@@ -918,16 +1353,16 @@
   metadata_expected.hdrCapacityMin = 1.0f;
   metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
   const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-  const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
+  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
 
   std::string xmp = generateXmpForSecondaryImage(metadata_expected);
 
   std::vector<uint8_t> xmpData;
   xmpData.reserve(nameSpaceLength + xmp.size());
   xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
-                  reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
+                 reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
   xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
-                  reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
+                 reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
 
   ultrahdr_metadata_struct metadata_read;
   EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
@@ -940,436 +1375,661 @@
   EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
 }
 
-/* Test Encode API-0 */
-TEST_F(JpegRTest, encodeFromP010) {
-  int ret;
+class JpegRAPIEncodeAndDecodeTest
+      : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> {
+public:
+  JpegRAPIEncodeAndDecodeTest()
+        : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){};
 
-  mRawP010Image.width = TEST_IMAGE_WIDTH;
-  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  // Load input files.
-  if (!loadP010Image(RAW_P010_IMAGE, &mRawP010Image, true)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  const ultrahdr_color_gamut mP010ColorGamut;
+  const ultrahdr_color_gamut mYuv420ColorGamut;
+};
+
+/* Test Encode API-0 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
+  // reference encode
+  UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010);
+  ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut));
+  ASSERT_TRUE(rawImg.allocateMemory());
+  ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName));
+  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+  ASSERT_TRUE(jpgImg.allocateMemory());
+  JpegR uHdrLib;
+  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                jpgImg.getImageHandle(), kQuality, nullptr),
+            OK);
+  // encode with luma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma and chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28));
+    ASSERT_TRUE(rawImg2.setChromaMode(false));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34));
+    ASSERT_TRUE(rawImg2.setChromaMode(false));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma and chroma stride set but no chroma ptr
+  {
+    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38));
+    ASSERT_TRUE(rawImg2.allocateMemory());
+    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
 
-  JpegR jpegRCodec;
-
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY,
-      nullptr);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
+  auto jpg1 = jpgImg.getImageHandle();
+#ifdef DUMP_OUTPUT
+  if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) {
+    std::cerr << "unable to write output file" << std::endl;
   }
+#endif
 
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH + 128;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  // Load input files.
-  if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithStride, true)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
-  }
-
-  jpegr_compressed_struct jpegRWithStride;
-  jpegRWithStride.maxLength = jpegR.length;
-  jpegRWithStride.data = malloc(jpegRWithStride.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegRWithStride,
-      DEFAULT_JPEG_QUALITY, nullptr);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  ASSERT_EQ(jpegR.length, jpegRWithStride.length)
-      << "Same input is yielding different output";
-  ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithStride.data, jpegR.length))
-      << "Same input is yielding different output";
-
-  mRawP010ImageWithChromaData.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithChromaData.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithChromaData.luma_stride = TEST_IMAGE_WIDTH + 64;
-  mRawP010ImageWithChromaData.chroma_stride = TEST_IMAGE_WIDTH + 256;
-  mRawP010ImageWithChromaData.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-  // Load input files.
-  if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithChromaData, false)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
-  }
-  jpegr_compressed_struct jpegRWithChromaData;
-  jpegRWithChromaData.maxLength = jpegR.length;
-  jpegRWithChromaData.data = malloc(jpegRWithChromaData.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithChromaData, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegRWithChromaData, DEFAULT_JPEG_QUALITY, nullptr);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  ASSERT_EQ(jpegR.length, jpegRWithChromaData.length)
-      << "Same input is yielding different output";
-  ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithChromaData.data, jpegR.length))
-      << "Same input is yielding different output";
-
-  free(jpegR.data);
-  free(jpegRWithStride.data);
-  free(jpegRWithChromaData.data);
+  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb"));
 }
 
-/* Test Encode API-0 and decode */
-TEST_F(JpegRTest, encodeFromP010ThenDecode) {
-  int ret;
-
-  // Load input files.
-  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+/* Test Encode API-1 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
+  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+  ASSERT_TRUE(rawImgP010.allocateMemory());
+  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
+  ASSERT_TRUE(rawImg420.allocateMemory());
+  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+  ASSERT_TRUE(jpgImg.allocateMemory());
+  JpegR uHdrLib;
+  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(),
+                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                jpgImg.getImageHandle(), kQuality, nullptr),
+            OK);
+  // encode with luma stride set p010
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  mRawP010Image.width = TEST_IMAGE_WIDTH;
-  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  JpegR jpegRCodec;
-
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY,
-      nullptr);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
+  // encode with luma and chroma stride set p010
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  if (SAVE_ENCODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)jpegR.data, jpegR.length);
+  // encode with chroma stride set p010
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma and chroma stride set but no chroma ptr p010
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma stride set 420
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma and chroma stride set 420
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34));
+    ASSERT_TRUE(rawImg2420.setChromaMode(false));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with chroma stride set 420
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38));
+    ASSERT_TRUE(rawImg2420.setChromaMode(false));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma and chroma stride set but no chroma ptr 420
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle(), kQuality, nullptr),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
 
-  jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
-  decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_DECODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-  }
+  auto jpg1 = jpgImg.getImageHandle();
 
-  free(jpegR.data);
-  free(decodedJpegR.data);
+#ifdef DUMP_OUTPUT
+  if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) {
+    std::cerr << "unable to write output file" << std::endl;
+  }
+#endif
+
+  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb"));
 }
 
-/* Test Encode API-0 (with stride) and decode */
-TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) {
-  int ret;
-
-  // Load input files.
-  if (!loadFile(RAW_P010_IMAGE_WITH_STRIDE, mRawP010ImageWithStride.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE_WITH_STRIDE << " failed";
+/* Test Encode API-2 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
+  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+  ASSERT_TRUE(rawImgP010.allocateMemory());
+  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
+  ASSERT_TRUE(rawImg420.allocateMemory());
+  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+  ASSERT_TRUE(jpgImg.allocateMemory());
+  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
+  ASSERT_TRUE(jpgSdr.allocateMemory());
+  auto sdr = jpgSdr.getImageHandle();
+  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
+  JpegR uHdrLib;
+  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                jpgImg.getImageHandle()),
+            OK);
+  // encode with luma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
-  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
-  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
-  mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  JpegR jpegRCodec;
-
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
+  // encode with luma and chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  if (SAVE_ENCODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)jpegR.data, jpegR.length);
+  // encode with chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with luma and chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256));
+    ASSERT_TRUE(rawImg2420.setChromaMode(false));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+  }
+  // encode with chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64));
+    ASSERT_TRUE(rawImg2420.setChromaMode(false));
+    ASSERT_TRUE(rawImg2420.allocateMemory());
+    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
 
-  jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
-  decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_DECODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-  }
+  auto jpg1 = jpgImg.getImageHandle();
 
-  free(jpegR.data);
-  free(decodedJpegR.data);
+#ifdef DUMP_OUTPUT
+  if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) {
+    std::cerr << "unable to write output file" << std::endl;
+  }
+#endif
+
+  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb"));
 }
 
-/* Test Encode API-1 and decode */
-TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) {
-  int ret;
-
-  // Load input files.
-  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+/* Test Encode API-3 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
+  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+  ASSERT_TRUE(rawImgP010.allocateMemory());
+  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+  ASSERT_TRUE(jpgImg.allocateMemory());
+  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
+  ASSERT_TRUE(jpgSdr.allocateMemory());
+  auto sdr = jpgSdr.getImageHandle();
+  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
+  JpegR uHdrLib;
+  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr,
+                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                jpgImg.getImageHandle()),
+            OK);
+  // encode with luma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  mRawP010Image.width = TEST_IMAGE_WIDTH;
-  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  // encode with luma and chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
-  JpegR jpegRCodec;
-
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
-      DEFAULT_JPEG_QUALITY, nullptr);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
+  // encode with chroma stride set
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
-  if (SAVE_ENCODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_input.jpgr";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)jpegR.data, jpegR.length);
+  // encode with luma and chroma stride set and no chroma ptr
+  {
+    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256));
+    ASSERT_TRUE(rawImg2P010.allocateMemory());
+    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+    ASSERT_TRUE(jpgImg2.allocateMemory());
+    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                                  jpgImg2.getImageHandle()),
+              OK);
+    auto jpg1 = jpgImg.getImageHandle();
+    auto jpg2 = jpgImg2.getImageHandle();
+    ASSERT_EQ(jpg1->length, jpg2->length);
+    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
   }
 
-  jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
-  decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_DECODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-  }
+  auto jpg1 = jpgImg.getImageHandle();
 
-  free(jpegR.data);
-  free(decodedJpegR.data);
+#ifdef DUMP_OUTPUT
+  if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) {
+    std::cerr << "unable to write output file" << std::endl;
+  }
+#endif
+
+  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb"));
 }
 
-/* Test Encode API-2 and decode */
-TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
-  int ret;
+INSTANTIATE_TEST_SUITE_P(
+        JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest,
+        ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
+                                             ULTRAHDR_COLORGAMUT_BT2100),
+                           ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
+                                             ULTRAHDR_COLORGAMUT_BT2100)));
 
-  // Load input files.
-  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
-  }
-  mRawP010Image.width = TEST_IMAGE_WIDTH;
-  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+// ============================================================================
+// Profiling
+// ============================================================================
 
-  if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
-  }
-  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
-  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+class Profiler {
+public:
+  void timerStart() { gettimeofday(&mStartingTime, nullptr); }
 
-  if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
-    FAIL() << "Load file " << JPEG_IMAGE << " failed";
-  }
-  mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+  void timerStop() { gettimeofday(&mEndingTime, nullptr); }
 
-  JpegR jpegRCodec;
-
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, &mRawYuv420Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-      &jpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_ENCODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_jpeg_input.jpgr";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)jpegR.data, jpegR.length);
+  int64_t elapsedTime() {
+    struct timeval elapsedMicroseconds;
+    elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec;
+    elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec;
+    return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec;
   }
 
-  jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
-  decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_DECODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-  }
+private:
+  struct timeval mStartingTime;
+  struct timeval mEndingTime;
+};
 
-  free(jpegR.data);
-  free(decodedJpegR.data);
+class JpegRBenchmark : public JpegR {
+public:
+  void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+                                ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
+  void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
+
+private:
+  const int kProfileCount = 10;
+};
+
+void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
+                                              jr_uncompressed_ptr p010Image,
+                                              ultrahdr_metadata_ptr metadata,
+                                              jr_uncompressed_ptr map) {
+  ASSERT_EQ(yuv420Image->width, p010Image->width);
+  ASSERT_EQ(yuv420Image->height, p010Image->height);
+  Profiler profileGenerateMap;
+  profileGenerateMap.timerStart();
+  for (auto i = 0; i < kProfileCount; i++) {
+    ASSERT_EQ(OK,
+              generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+                              metadata, map));
+    if (i != kProfileCount - 1) delete[] static_cast<uint8_t*>(map->data);
+  }
+  profileGenerateMap.timerStop();
+  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
+        profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f));
 }
 
-/* Test Encode API-3 and decode */
-TEST_F(JpegRTest, encodeFromJpegThenDecode) {
-  int ret;
-
-  // Load input files.
-  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+                                           ultrahdr_metadata_ptr metadata,
+                                           jr_uncompressed_ptr dest) {
+  Profiler profileRecMap;
+  profileRecMap.timerStart();
+  for (auto i = 0; i < kProfileCount; i++) {
+    ASSERT_EQ(OK,
+              applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
+                           metadata->maxContentBoost /* displayBoost */, dest));
   }
-  mRawP010Image.width = TEST_IMAGE_WIDTH;
-  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  if (SAVE_INPUT_RGBA) {
-    size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t);
-    uint32_t *data = (uint32_t *)malloc(rgbaSize);
-
-    for (size_t y = 0; y < mRawP010Image.height; ++y) {
-      for (size_t x = 0; x < mRawP010Image.width; ++x) {
-        Color hdr_yuv_gamma = getP010Pixel(&mRawP010Image, x, y);
-        Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-        uint32_t rgba1010102 = colorToRgba1010102(hdr_rgb_gamma);
-        size_t pixel_idx =  x + y * mRawP010Image.width;
-        reinterpret_cast<uint32_t*>(data)[pixel_idx] = rgba1010102;
-      }
-    }
-
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/input_from_p010.rgb10";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)data, rgbaSize);
-    free(data);
-  }
-  if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
-    FAIL() << "Load file " << JPEG_IMAGE << " failed";
-  }
-  mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
-  JpegR jpegRCodec;
-
-  jpegr_compressed_struct jpegR;
-  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-  jpegR.data = malloc(jpegR.maxLength);
-  ret = jpegRCodec.encodeJPEGR(
-      &mRawP010Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_ENCODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_p010_jpeg_input.jpgr";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)jpegR.data, jpegR.length);
-  }
-
-  jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
-  decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
-  if (ret != OK) {
-    FAIL() << "Error code is " << ret;
-  }
-  if (SAVE_DECODING_RESULT) {
-    // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb";
-    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-    if (!imageFile.is_open()) {
-      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-    }
-    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-  }
-
-  free(jpegR.data);
-  free(decodedJpegR.data);
+  profileRecMap.timerStop();
+  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
+        profileRecMap.elapsedTime() / (kProfileCount * 1000.f));
 }
 
-TEST_F(JpegRTest, ProfileGainMapFuncs) {
-  const size_t kWidth = TEST_IMAGE_WIDTH;
-  const size_t kHeight = TEST_IMAGE_HEIGHT;
-
-  // Load input files.
-  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+TEST(JpegRTest, ProfileGainMapFuncs) {
+  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+  ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+  ASSERT_TRUE(rawImgP010.allocateMemory());
+  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+  ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+  ASSERT_TRUE(rawImg420.allocateMemory());
+  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+  ultrahdr_metadata_struct metadata = {.version = "1.0"};
+  jpegr_uncompressed_struct map = {.data = NULL,
+                                   .width = 0,
+                                   .height = 0,
+                                   .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+  {
+    auto rawImg = rawImgP010.getImageHandle();
+    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
+    if (!rawImg->chroma_data) {
+      uint16_t* data = reinterpret_cast<uint16_t*>(rawImg->data);
+      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
+      rawImg->chroma_stride = rawImg->luma_stride;
+    }
   }
-  mRawP010Image.width = kWidth;
-  mRawP010Image.height = kHeight;
-  mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
-  if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
-    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  {
+    auto rawImg = rawImg420.getImageHandle();
+    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
+    if (!rawImg->chroma_data) {
+      uint8_t* data = reinterpret_cast<uint8_t*>(rawImg->data);
+      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
+      rawImg->chroma_stride = rawImg->luma_stride / 2;
+    }
   }
-  mRawYuv420Image.width = kWidth;
-  mRawYuv420Image.height = kHeight;
-  mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
 
   JpegRBenchmark benchmark;
+  ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(),
+                                                             rawImgP010.getImageHandle(), &metadata,
+                                                             &map));
 
-  ultrahdr_metadata_struct metadata = { .version = "1.0" };
-
-  jpegr_uncompressed_struct map = { .data = NULL,
+  const int dstSize = kImageWidth * kImageWidth * 4;
+  auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
+  jpegr_uncompressed_struct dest = {.data = bufferDst.get(),
                                     .width = 0,
                                     .height = 0,
-                                    .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED };
+                                    .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
 
-  benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
-
-  const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4;
-  auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
-  jpegr_uncompressed_struct dest = { .data = bufferDst.get(),
-                                     .width = 0,
-                                     .height = 0,
-                                     .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED };
-
-  benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest);
+  ASSERT_NO_FATAL_FAILURE(
+          benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest));
 }
 
 } // namespace android::ultrahdr
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 654e5b7..04e2fff 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -41,24 +41,44 @@
 /*
  * EGL userspace drivers must be provided either:
  * - as a single library:
- *      /vendor/lib/egl/libGLES.so
+ *      /vendor/${LIB}/egl/libGLES.so
  *
  * - as separate libraries:
- *      /vendor/lib/egl/libEGL.so
- *      /vendor/lib/egl/libGLESv1_CM.so
- *      /vendor/lib/egl/libGLESv2.so
+ *      /vendor/${LIB}/egl/libEGL.so
+ *      /vendor/${LIB}/egl/libGLESv1_CM.so
+ *      /vendor/${LIB}/egl/libGLESv2.so
  *
  * For backward compatibility and to facilitate the transition to
  * this new naming scheme, the loader will additionally look for:
  *
- *      /{vendor|system}/lib/egl/lib{GLES | [EGL|GLESv1_CM|GLESv2]}_*.so
+ *      /vendor/${LIB}/egl/lib{GLES | [EGL|GLESv1_CM|GLESv2]}_${SUFFIX}.so
  *
  */
 
-Loader& Loader::getInstance() {
-    static Loader loader;
-    return loader;
-}
+#ifndef SYSTEM_LIB_PATH
+#if defined(__LP64__)
+#define SYSTEM_LIB_PATH "/system/lib64"
+#else
+#define SYSTEM_LIB_PATH "/system/lib"
+#endif
+#endif
+
+static const char* PERSIST_DRIVER_SUFFIX_PROPERTY = "persist.graphics.egl";
+static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
+static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
+
+static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
+        PERSIST_DRIVER_SUFFIX_PROPERTY,
+        RO_DRIVER_SUFFIX_PROPERTY,
+        RO_BOARD_PLATFORM_PROPERTY,
+};
+
+static const char* const VENDOR_LIB_EGL_DIR =
+#if defined(__LP64__)
+        "/vendor/lib64/egl";
+#else
+        "/vendor/lib/egl";
+#endif
 
 static void* do_dlopen(const char* path, int mode) {
     ATRACE_CALL();
@@ -80,6 +100,17 @@
     return android_unload_sphal_library(dso);
 }
 
+static void* load_wrapper(const char* path) {
+    void* so = do_dlopen(path, RTLD_NOW | RTLD_LOCAL);
+    ALOGE_IF(!so, "dlopen(\"%s\") failed: %s", path, dlerror());
+    return so;
+}
+
+Loader& Loader::getInstance() {
+    static Loader loader;
+    return loader;
+}
+
 Loader::driver_t::driver_t(void* gles)
 {
     dso[0] = gles;
@@ -123,30 +154,6 @@
 Loader::~Loader() {
 }
 
-static void* load_wrapper(const char* path) {
-    void* so = do_dlopen(path, RTLD_NOW | RTLD_LOCAL);
-    ALOGE_IF(!so, "dlopen(\"%s\") failed: %s", path, dlerror());
-    return so;
-}
-
-#ifndef EGL_WRAPPER_DIR
-#if defined(__LP64__)
-#define EGL_WRAPPER_DIR "/system/lib64"
-#else
-#define EGL_WRAPPER_DIR "/system/lib"
-#endif
-#endif
-
-static const char* PERSIST_DRIVER_SUFFIX_PROPERTY = "persist.graphics.egl";
-static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
-static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
-
-static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
-        PERSIST_DRIVER_SUFFIX_PROPERTY,
-        RO_DRIVER_SUFFIX_PROPERTY,
-        RO_BOARD_PLATFORM_PROPERTY,
-};
-
 // Check whether the loaded system drivers should be unloaded in order to
 // load ANGLE or the updatable graphics drivers.
 // If ANGLE namespace is set, it means the application is identified to run on top of ANGLE.
@@ -323,13 +330,13 @@
                         HAL_SUBNAME_KEY_PROPERTIES[2]);
 
     if (!cnx->libEgl) {
-        cnx->libEgl = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so");
+        cnx->libEgl = load_wrapper(SYSTEM_LIB_PATH "/libEGL.so");
     }
     if (!cnx->libGles1) {
-        cnx->libGles1 = load_wrapper(EGL_WRAPPER_DIR "/libGLESv1_CM.so");
+        cnx->libGles1 = load_wrapper(SYSTEM_LIB_PATH "/libGLESv1_CM.so");
     }
     if (!cnx->libGles2) {
-        cnx->libGles2 = load_wrapper(EGL_WRAPPER_DIR "/libGLESv2.so");
+        cnx->libGles2 = load_wrapper(SYSTEM_LIB_PATH "/libGLESv2.so");
     }
 
     if (!cnx->libEgl || !cnx->libGles2 || !cnx->libGles1) {
@@ -432,31 +439,19 @@
     class MatchFile {
     public:
         static std::string find(const char* libraryName, const bool exact) {
-            const char* const searchPaths[] = {
-#if defined(__LP64__)
-                    "/vendor/lib64/egl",
-                    "/system/lib64/egl"
-#else
-                    "/vendor/lib/egl",
-                    "/system/lib/egl"
-#endif
-            };
-
-            for (auto dir : searchPaths) {
-                std::string absolutePath;
-                if (find(absolutePath, libraryName, dir, exact)) {
-                    return absolutePath;
-                }
+            std::string absolutePath;
+            if (findLibPath(absolutePath, libraryName, exact)) {
+                return absolutePath;
             }
 
             // Driver not found. gah.
             return std::string();
         }
     private:
-        static bool find(std::string& result,
-                const std::string& pattern, const char* const search, bool exact) {
+        static bool findLibPath(std::string& result, const std::string& pattern, bool exact) {
+            const std::string vendorLibEglDirString = std::string(VENDOR_LIB_EGL_DIR);
             if (exact) {
-                std::string absolutePath = std::string(search) + "/" + pattern + ".so";
+                std::string absolutePath = vendorLibEglDirString + "/" + pattern + ".so";
                 if (!access(absolutePath.c_str(), R_OK)) {
                     result = absolutePath;
                     return true;
@@ -464,7 +459,7 @@
                 return false;
             }
 
-            DIR* d = opendir(search);
+            DIR* d = opendir(VENDOR_LIB_EGL_DIR);
             if (d != nullptr) {
                 struct dirent* e;
                 while ((e = readdir(d)) != nullptr) {
@@ -477,7 +472,7 @@
                     }
                     if (strstr(e->d_name, pattern.c_str()) == e->d_name) {
                         if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
-                            result = std::string(search) + "/" + e->d_name;
+                            result = vendorLibEglDirString + "/" + e->d_name;
                             closedir(d);
                             return true;
                         }
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index 9a6bb7a..ee605c2 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -35,6 +35,7 @@
 
 namespace angle {
 
+constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
 constexpr int kAngleDlFlags = RTLD_LOCAL | RTLD_NOW;
 
 static GetDisplayPlatformFunc angleGetDisplayPlatform = nullptr;
@@ -115,8 +116,6 @@
     android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
     void* so = nullptr;
     if (ns) {
-        // Loading from an APK, so hard-code the suffix to "_angle".
-        constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
         const android_dlextinfo dlextinfo = {
                 .flags = ANDROID_DLEXT_USE_NAMESPACE,
                 .library_namespace = ns,
@@ -130,19 +129,11 @@
         }
     } else {
         // If we are here, ANGLE is loaded as built-in gl driver in the sphal.
-        // Get the specified ANGLE library filename suffix.
-        std::string angleEs2LibSuffix = android::base::GetProperty("ro.hardware.egl", "");
-        if (angleEs2LibSuffix.empty()) {
-            ALOGE("%s failed to get valid ANGLE library filename suffix!", __FUNCTION__);
-            return false;
-        }
-
-        std::string angleEs2LibName = "libGLESv2_" + angleEs2LibSuffix + ".so";
-        so = android_load_sphal_library(angleEs2LibName.c_str(), kAngleDlFlags);
+        so = android_load_sphal_library(kAngleEs2Lib, kAngleDlFlags);
         if (so) {
-            ALOGD("dlopen (%s) success at %p", angleEs2LibName.c_str(), so);
+            ALOGD("dlopen (%s) success at %p", kAngleEs2Lib, so);
         } else {
-            ALOGE("%s failed to dlopen %s!", __FUNCTION__, angleEs2LibName.c_str());
+            ALOGE("%s failed to dlopen %s: %s!", __FUNCTION__, kAngleEs2Lib, dlerror());
             return false;
         }
     }
diff --git a/services/displayservice/Android.bp b/services/displayservice/Android.bp
index 8681784..c88f2fc 100644
--- a/services/displayservice/Android.bp
+++ b/services/displayservice/Android.bp
@@ -23,7 +23,7 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_library_static {
     name: "libdisplayservicehidl",
 
     srcs: [
@@ -37,18 +37,24 @@
         "libgui",
         "libhidlbase",
         "libutils",
+    ],
+
+    static_libs: [
         "android.frameworks.displayservice@1.0",
     ],
 
     export_include_dirs: ["include"],
     export_shared_lib_headers: [
-        "android.frameworks.displayservice@1.0",
         "libgui",
         "libutils",
     ],
 
+    export_static_lib_headers: [
+        "android.frameworks.displayservice@1.0",
+    ],
+
     cflags: [
         "-Werror",
         "-Wall",
-    ]
+    ],
 }
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index 052efb6b..e5b1e44 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -21,7 +21,13 @@
 
 cc_defaults {
     name: "libgpuservice_defaults",
-    defaults: ["gpuservice_defaults"],
+    defaults: [
+        "gpuservice_defaults",
+        "libvkjson_deps",
+        "libgfxstats_deps",
+        "libgpumem_deps",
+        "libgpumemtracer_deps",
+    ],
     cflags: [
         "-DLOG_TAG=\"GpuService\"",
     ],
@@ -29,17 +35,17 @@
         "libbase",
         "libbinder",
         "libcutils",
-        "libgfxstats",
-        "libgpumem",
         "libgpuwork",
-        "libgpumemtracer",
         "libgraphicsenv",
         "liblog",
         "libutils",
-        "libvkjson",
     ],
     static_libs: [
+        "libgfxstats",
+        "libgpumem",
+        "libgpumemtracer",
         "libserviceutils",
+        "libvkjson",
     ],
     export_static_lib_headers: [
         "libserviceutils",
@@ -68,7 +74,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library_static {
     name: "libgpuservice",
     defaults: ["libgpuservice_production_defaults"],
     export_include_dirs: ["include"],
@@ -96,14 +102,17 @@
 
 cc_binary {
     name: "gpuservice",
-    defaults: ["libgpuservice_binary"],
+    defaults: [
+        "libgpuservice_binary",
+        "libgpuservice_production_defaults",
+    ],
     init_rc: ["gpuservice.rc"],
     required: [
         "bpfloader",
         "gpuMem.o",
     ],
     srcs: [":gpuservice_binary_sources"],
-    shared_libs: [
+    static_libs: [
         "libgpuservice",
     ],
 }
diff --git a/services/gpuservice/gpumem/Android.bp b/services/gpuservice/gpumem/Android.bp
index d0ea856..66a3059 100644
--- a/services/gpuservice/gpumem/Android.bp
+++ b/services/gpuservice/gpumem/Android.bp
@@ -21,12 +21,8 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
-    name: "libgpumem",
-    srcs: [
-        "GpuMem.cpp",
-    ],
-    header_libs: ["bpf_headers"],
+cc_defaults {
+    name: "libgpumem_deps",
     shared_libs: [
         "libbase",
         "libbpf_bcc",
@@ -34,6 +30,17 @@
         "liblog",
         "libutils",
     ],
+}
+
+cc_library_static {
+    name: "libgpumem",
+    defaults: [
+        "libgpumem_deps",
+    ],
+    srcs: [
+        "GpuMem.cpp",
+    ],
+    header_libs: ["bpf_headers"],
     export_include_dirs: ["include"],
     export_header_lib_headers: ["bpf_headers"],
     export_shared_lib_headers: ["libbase"],
diff --git a/services/gpuservice/gpustats/Android.bp b/services/gpuservice/gpustats/Android.bp
index 54291ad..0e64716 100644
--- a/services/gpuservice/gpustats/Android.bp
+++ b/services/gpuservice/gpustats/Android.bp
@@ -7,11 +7,8 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
-    name: "libgfxstats",
-    srcs: [
-        "GpuStats.cpp",
-    ],
+cc_defaults {
+    name: "libgfxstats_deps",
     shared_libs: [
         "libcutils",
         "libgraphicsenv",
@@ -22,6 +19,16 @@
         "libstatssocket",
         "libutils",
     ],
+}
+
+cc_library_static {
+    name: "libgfxstats",
+    defaults: [
+        "libgfxstats_deps",
+    ],
+    srcs: [
+        "GpuStats.cpp",
+    ],
     export_include_dirs: ["include"],
     export_shared_lib_headers: [
         "libstatspull",
diff --git a/services/gpuservice/tests/fuzzers/Android.bp b/services/gpuservice/tests/fuzzers/Android.bp
index 6bcc5e8..d4d48c4 100644
--- a/services/gpuservice/tests/fuzzers/Android.bp
+++ b/services/gpuservice/tests/fuzzers/Android.bp
@@ -5,10 +5,12 @@
 cc_fuzz {
     name: "gpu_service_fuzzer",
     defaults: [
+        "libgpuservice_defaults",
         "service_fuzzer_defaults",
         "fuzzer_disable_leaks",
     ],
     static_libs: [
+        "libgpuservice",
         "liblog",
     ],
     fuzz_config: {
@@ -20,7 +22,4 @@
     },
     include_dirs: ["frameworks/native/services/gpuservice/"],
     srcs: ["GpuServiceFuzzer.cpp"],
-    shared_libs: [
-        "libgpuservice",
-    ],
 }
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index c870b17..8056a2c 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -24,6 +24,9 @@
 cc_test {
     name: "gpuservice_unittest",
     test_suites: ["device-tests"],
+    defaults: [
+        "libgpuservice_defaults",
+    ],
     srcs: [
         "GpuMemTest.cpp",
         "GpuMemTracerTest.cpp",
@@ -36,9 +39,6 @@
         "libbinder",
         "libbpf_bcc",
         "libcutils",
-        "libgfxstats",
-        "libgpumem",
-        "libgpumemtracer",
         "libgraphicsenv",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -46,10 +46,10 @@
         "libstatslog",
         "libstatspull",
         "libutils",
-        "libgpuservice",
     ],
     static_libs: [
         "libgmock",
+        "libgpuservice",
         "libperfetto_client_experimental",
         "perfetto_trace_protos",
     ],
diff --git a/services/gpuservice/tracing/Android.bp b/services/gpuservice/tracing/Android.bp
index a1bc1ed..d636b7d 100644
--- a/services/gpuservice/tracing/Android.bp
+++ b/services/gpuservice/tracing/Android.bp
@@ -21,20 +21,28 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
-    name: "libgpumemtracer",
-    srcs: [
-        "GpuMemTracer.cpp",
-    ],
+cc_defaults {
+    name: "libgpumemtracer_deps",
     shared_libs: [
-        "libgpumem",
         "libbase",
         "liblog",
         "libutils",
     ],
     static_libs: [
+        "libgpumem",
         "libperfetto_client_experimental",
     ],
+}
+
+cc_library_static {
+    name: "libgpumemtracer",
+    defaults: [
+        "libgpumemtracer_deps",
+        "libgpumem_deps",
+    ],
+    srcs: [
+        "GpuMemTracer.cpp",
+    ],
     export_include_dirs: ["include"],
     export_static_lib_headers: [
         "libperfetto_client_experimental",
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index dc7c75a..76729ef 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -28,6 +28,7 @@
 
 cc_defaults {
     name: "inputflinger_defaults",
+    host_supported: true,
     cpp_std: "c++20",
     cflags: [
         "-Wall",
@@ -50,6 +51,20 @@
         "-*", // Disable all checks not explicitly enabled for now
     ] + inputflinger_tidy_checks,
     tidy_checks_as_errors: inputflinger_tidy_checks,
+    target: {
+        host: {
+            sanitize: {
+                address: true,
+            },
+            include_dirs: [
+                "bionic/libc/kernel/android/uapi/",
+                "bionic/libc/kernel/uapi",
+            ],
+            cflags: [
+                "-D__ANDROID_HOST__",
+            ],
+        },
+    },
 }
 
 /////////////////////////////////////////////////
@@ -62,6 +77,7 @@
         "InputCommonConverter.cpp",
         "InputDeviceMetricsCollector.cpp",
         "InputProcessor.cpp",
+        "PointerChoreographer.cpp",
         "PreferStylusOverTouchBlocker.cpp",
         "UnwantedInteractionBlocker.cpp",
     ],
@@ -72,6 +88,7 @@
     srcs: [":libinputflinger_sources"],
     shared_libs: [
         "android.hardware.input.processor-V1-ndk",
+        "com.android.server.inputflinger-ndk",
         "libbase",
         "libbinder",
         "libbinder_ndk",
@@ -79,6 +96,7 @@
         "libcrypto",
         "libcutils",
         "libhidlbase",
+        "libinput",
         "libkll",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -91,34 +109,34 @@
         "libpalmrejection",
         "libui-types",
     ],
+    generated_headers: [
+        "cxx-bridge-header",
+        "inputflinger_rs_bootstrap_bridge_header",
+    ],
+    header_libs: ["inputflinger_rs_bootstrap_cxx_headers"],
+    generated_sources: ["inputflinger_rs_bootstrap_bridge_code"],
+    whole_static_libs: ["libinputflinger_rs"],
+    export_shared_lib_headers: ["com.android.server.inputflinger-ndk"],
     target: {
         android: {
             shared_libs: [
                 "libgui",
-                "libinput",
                 "libstatspull",
                 "libstatssocket",
             ],
         },
         host: {
             static_libs: [
-                "libinput",
                 "libstatspull",
                 "libstatssocket",
             ],
-            include_dirs: [
-                "bionic/libc/kernel/android/uapi/",
-                "bionic/libc/kernel/uapi",
-            ],
-            cflags: [
-                "-D__ANDROID_HOST__",
-            ],
         },
     },
 }
 
 cc_library_shared {
     name: "libinputflinger",
+    host_supported: true,
     defaults: [
         "inputflinger_defaults",
         "libinputflinger_defaults",
@@ -164,6 +182,7 @@
 filegroup {
     name: "libinputflinger_base_sources",
     srcs: [
+        "InputDeviceMetricsSource.cpp",
         "InputListener.cpp",
         "InputReaderBase.cpp",
         "InputThread.cpp",
@@ -179,25 +198,14 @@
         "libbase",
         "libbinder",
         "libcutils",
+        "libinput",
         "liblog",
+        "libstatslog",
         "libutils",
     ],
     header_libs: [
         "libinputflinger_headers",
     ],
-    target: {
-        android: {
-            shared_libs: [
-                "libinput",
-            ],
-        },
-        host: {
-            static_libs: [
-                "libinput",
-                "libui-types",
-            ],
-        },
-    },
 }
 
 cc_library_shared {
@@ -224,6 +232,7 @@
         // native targets
         "libgui_test",
         "libinput",
+        "libinputreader_static",
         "libinputflinger",
         "inputflinger_tests",
         "inputflinger_benchmarks",
@@ -233,9 +242,6 @@
         "libinputservice_test",
         "Bug-115739809",
         "StructLayout_test",
-        // currently unused, but still must build correctly
-        "inputflinger",
-        "libinputflingerhost",
 
         // rust targets
         "libinput_rust_test",
@@ -246,12 +252,13 @@
         "inputflinger_keyboard_input_fuzzer",
         "inputflinger_multitouch_input_fuzzer",
         "inputflinger_switch_input_fuzzer",
+        "inputflinger_touchpad_input_fuzzer",
         "inputflinger_input_reader_fuzzer",
         "inputflinger_blocking_queue_fuzzer",
         "inputflinger_input_classifier_fuzzer",
 
         // Java/Kotlin targets
-        "CtsWindowManagerDeviceTestCases",
+        "CtsWindowManagerDeviceWindow",
         "InputTests",
         "CtsHardwareTestCases",
         "CtsInputTestCases",
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 56a3fb4..cefb140 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -17,11 +17,10 @@
 #define LOG_TAG "InputDeviceMetricsCollector"
 #include "InputDeviceMetricsCollector.h"
 
-#include "KeyCodeClassifications.h"
+#include "InputDeviceMetricsSource.h"
 
 #include <android-base/stringprintf.h>
 #include <input/PrintTools.h>
-#include <linux/input.h>
 
 namespace android {
 
@@ -64,14 +63,13 @@
 class : public InputDeviceMetricsLogger {
     nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
 
-    void logInputDeviceUsageReported(const InputDeviceInfo& info,
+    void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         const int32_t durationMillis =
                 std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
         const static std::vector<int32_t> empty;
-        const auto& identifier = info.getIdentifier();
 
-        ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
+        ALOGD_IF(DEBUG, "Usage session reported for device id: %d", info.deviceId);
         ALOGD_IF(DEBUG, "    Total duration: %dms", durationMillis);
         ALOGD_IF(DEBUG, "    Source breakdown:");
 
@@ -96,11 +94,9 @@
             ALOGD_IF(DEBUG, "        - uid: %s\t duration: %dms", uid.toString().c_str(),
                      durMillis);
         }
-        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
-                          identifier.version,
-                          linuxBusToInputDeviceBusEnum(identifier.bus,
-                                                       info.getUsiVersion().has_value()),
-                          durationMillis, sources, durationsPerSource, uids, durationsPerUid);
+        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, info.vendor, info.product, info.version,
+                          linuxBusToInputDeviceBusEnum(info.bus, info.isUsiStylus), durationMillis,
+                          sources, durationsPerSource, uids, durationsPerUid);
     }
 } sStatsdLogger;
 
@@ -116,96 +112,6 @@
 
 } // namespace
 
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo& info,
-                                                const NotifyKeyArgs& keyArgs) {
-    if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
-        return InputDeviceUsageSource::UNKNOWN;
-    }
-
-    if (isFromSource(keyArgs.source, AINPUT_SOURCE_DPAD) &&
-        DPAD_ALL_KEYCODES.count(keyArgs.keyCode) != 0) {
-        return InputDeviceUsageSource::DPAD;
-    }
-
-    if (isFromSource(keyArgs.source, AINPUT_SOURCE_GAMEPAD) &&
-        GAMEPAD_KEYCODES.count(keyArgs.keyCode) != 0) {
-        return InputDeviceUsageSource::GAMEPAD;
-    }
-
-    if (info.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
-        return InputDeviceUsageSource::KEYBOARD;
-    }
-
-    return InputDeviceUsageSource::BUTTONS;
-}
-
-std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs& motionArgs) {
-    LOG_ALWAYS_FATAL_IF(motionArgs.getPointerCount() < 1, "Received motion args without pointers");
-    std::set<InputDeviceUsageSource> sources;
-
-    for (uint32_t i = 0; i < motionArgs.getPointerCount(); i++) {
-        const auto toolType = motionArgs.pointerProperties[i].toolType;
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE)) {
-            if (toolType == ToolType::MOUSE) {
-                sources.emplace(InputDeviceUsageSource::MOUSE);
-                continue;
-            }
-            if (toolType == ToolType::FINGER) {
-                sources.emplace(InputDeviceUsageSource::TOUCHPAD);
-                continue;
-            }
-            if (isStylusToolType(toolType)) {
-                sources.emplace(InputDeviceUsageSource::STYLUS_INDIRECT);
-                continue;
-            }
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE_RELATIVE) &&
-            toolType == ToolType::MOUSE) {
-            sources.emplace(InputDeviceUsageSource::MOUSE_CAPTURED);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHPAD) &&
-            toolType == ToolType::FINGER) {
-            sources.emplace(InputDeviceUsageSource::TOUCHPAD_CAPTURED);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_BLUETOOTH_STYLUS) &&
-            isStylusToolType(toolType)) {
-            sources.emplace(InputDeviceUsageSource::STYLUS_FUSED);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(toolType)) {
-            sources.emplace(InputDeviceUsageSource::STYLUS_DIRECT);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCH_NAVIGATION)) {
-            sources.emplace(InputDeviceUsageSource::TOUCH_NAVIGATION);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_JOYSTICK)) {
-            sources.emplace(InputDeviceUsageSource::JOYSTICK);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_ROTARY_ENCODER)) {
-            sources.emplace(InputDeviceUsageSource::ROTARY_ENCODER);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TRACKBALL)) {
-            sources.emplace(InputDeviceUsageSource::TRACKBALL);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHSCREEN)) {
-            sources.emplace(InputDeviceUsageSource::TOUCHSCREEN);
-            continue;
-        }
-        sources.emplace(InputDeviceUsageSource::UNKNOWN);
-    }
-
-    return sources;
-}
-
-// --- InputDeviceMetricsCollector ---
-
 InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
       : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}
 
@@ -232,8 +138,8 @@
 
 void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
     reportCompletedSessions();
-    const SourceProvider getSources = [&args](const InputDeviceInfo& info) {
-        return std::set{getUsageSourceForKeyArgs(info, args)};
+    const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
+        return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
     };
     onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
 
@@ -291,13 +197,23 @@
 }
 
 void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
-    std::map<DeviceId, InputDeviceInfo> newDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
 
     for (const InputDeviceInfo& info : infos) {
         if (isIgnoredInputDeviceId(info.getId())) {
             continue;
         }
-        newDeviceInfos.emplace(info.getId(), info);
+        const auto& i = info.getIdentifier();
+        newDeviceInfos.emplace(info.getId(),
+                               MetricsDeviceInfo{
+                                       .deviceId = info.getId(),
+                                       .vendor = i.vendor,
+                                       .product = i.product,
+                                       .version = i.version,
+                                       .bus = i.bus,
+                                       .isUsiStylus = info.getUsiVersion().has_value(),
+                                       .keyboardType = info.getKeyboardType(),
+                               });
     }
 
     for (auto [deviceId, info] : mLoggedDeviceInfos) {
@@ -311,7 +227,7 @@
 }
 
 void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
-                                                       const InputDeviceInfo& info) {
+                                                       const MetricsDeviceInfo& info) {
     auto it = mActiveUsageSessions.find(deviceId);
     if (it == mActiveUsageSessions.end()) {
         return;
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 1f7c5d9..9633664 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "InputDeviceMetricsSource.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 #include "SyncQueue.h"
@@ -23,7 +24,6 @@
 #include <ftl/mixins.h>
 #include <gui/WindowInfo.h>
 #include <input/InputDevice.h>
-#include <statslog.h>
 #include <chrono>
 #include <functional>
 #include <map>
@@ -52,38 +52,6 @@
     virtual void dump(std::string& dump) = 0;
 };
 
-/**
- * Enum representation of the InputDeviceUsageSource.
- */
-enum class InputDeviceUsageSource : int32_t {
-    UNKNOWN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__UNKNOWN,
-    BUTTONS = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__BUTTONS,
-    KEYBOARD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__KEYBOARD,
-    DPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__DPAD,
-    GAMEPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__GAMEPAD,
-    JOYSTICK = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__JOYSTICK,
-    MOUSE = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE,
-    MOUSE_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE_CAPTURED,
-    TOUCHPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD,
-    TOUCHPAD_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD_CAPTURED,
-    ROTARY_ENCODER = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__ROTARY_ENCODER,
-    STYLUS_DIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_DIRECT,
-    STYLUS_INDIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_INDIRECT,
-    STYLUS_FUSED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_FUSED,
-    TOUCH_NAVIGATION = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCH_NAVIGATION,
-    TOUCHSCREEN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHSCREEN,
-    TRACKBALL = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TRACKBALL,
-
-    ftl_first = UNKNOWN,
-    ftl_last = TRACKBALL,
-};
-
-/** Returns the InputDeviceUsageSource that corresponds to the key event. */
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo&, const NotifyKeyArgs&);
-
-/** Returns the InputDeviceUsageSources that correspond to the motion event. */
-std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
-
 /** The logging interface for the metrics collector, injected for testing. */
 class InputDeviceMetricsLogger {
 public:
@@ -110,7 +78,19 @@
         UidUsageBreakdown uidBreakdown;
     };
 
-    virtual void logInputDeviceUsageReported(const InputDeviceInfo&, const DeviceUsageReport&) = 0;
+    // A subset of information from the InputDeviceInfo class that is used for metrics collection,
+    // used to avoid copying and storing all of the fields and strings in InputDeviceInfo.
+    struct MetricsDeviceInfo {
+        int32_t deviceId;
+        int32_t vendor;
+        int32_t product;
+        int32_t version;
+        int32_t bus;
+        bool isUsiStylus;
+        int32_t keyboardType;
+    };
+    virtual void logInputDeviceUsageReported(const MetricsDeviceInfo&,
+                                             const DeviceUsageReport&) = 0;
     virtual ~InputDeviceMetricsLogger() = default;
 };
 
@@ -153,8 +133,9 @@
     }
 
     using Uid = gui::Uid;
+    using MetricsDeviceInfo = InputDeviceMetricsLogger::MetricsDeviceInfo;
 
-    std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos;
 
     using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
     SyncQueue<Interaction> mInteractionsQueue;
@@ -188,8 +169,9 @@
     std::map<DeviceId, ActiveSession> mActiveUsageSessions;
 
     void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
-    void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceInfo& info);
-    using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
+    void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info);
+    using SourceProvider =
+            std::function<std::set<InputDeviceUsageSource>(const MetricsDeviceInfo&)>;
     void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
                             const SourceProvider& getSources);
     void onInputDeviceInteraction(const Interaction&);
diff --git a/services/inputflinger/InputDeviceMetricsSource.cpp b/services/inputflinger/InputDeviceMetricsSource.cpp
new file mode 100644
index 0000000..dee4cb8
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsSource.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "InputDeviceMetricsSource.h"
+
+#include "KeyCodeClassifications.h"
+
+#include <android/input.h>
+#include <input/Input.h>
+#include <linux/input.h>
+#include <log/log_main.h>
+
+#include <set>
+
+namespace android {
+
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
+                                                const NotifyKeyArgs& keyArgs) {
+    if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
+        return InputDeviceUsageSource::UNKNOWN;
+    }
+
+    if (isFromSource(keyArgs.source, AINPUT_SOURCE_DPAD) &&
+        DPAD_ALL_KEYCODES.count(keyArgs.keyCode) != 0) {
+        return InputDeviceUsageSource::DPAD;
+    }
+
+    if (isFromSource(keyArgs.source, AINPUT_SOURCE_GAMEPAD) &&
+        GAMEPAD_KEYCODES.count(keyArgs.keyCode) != 0) {
+        return InputDeviceUsageSource::GAMEPAD;
+    }
+
+    if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+        return InputDeviceUsageSource::KEYBOARD;
+    }
+
+    return InputDeviceUsageSource::BUTTONS;
+}
+
+std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs& motionArgs) {
+    LOG_ALWAYS_FATAL_IF(motionArgs.getPointerCount() < 1, "Received motion args without pointers");
+    std::set<InputDeviceUsageSource> sources;
+
+    for (uint32_t i = 0; i < motionArgs.getPointerCount(); i++) {
+        const auto toolType = motionArgs.pointerProperties[i].toolType;
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE)) {
+            if (toolType == ToolType::MOUSE) {
+                sources.emplace(InputDeviceUsageSource::MOUSE);
+                continue;
+            }
+            if (toolType == ToolType::FINGER) {
+                sources.emplace(InputDeviceUsageSource::TOUCHPAD);
+                continue;
+            }
+            if (isStylusToolType(toolType)) {
+                sources.emplace(InputDeviceUsageSource::STYLUS_INDIRECT);
+                continue;
+            }
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE_RELATIVE) &&
+            toolType == ToolType::MOUSE) {
+            sources.emplace(InputDeviceUsageSource::MOUSE_CAPTURED);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHPAD) &&
+            toolType == ToolType::FINGER) {
+            sources.emplace(InputDeviceUsageSource::TOUCHPAD_CAPTURED);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_BLUETOOTH_STYLUS) &&
+            isStylusToolType(toolType)) {
+            sources.emplace(InputDeviceUsageSource::STYLUS_FUSED);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(toolType)) {
+            sources.emplace(InputDeviceUsageSource::STYLUS_DIRECT);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCH_NAVIGATION)) {
+            sources.emplace(InputDeviceUsageSource::TOUCH_NAVIGATION);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_JOYSTICK)) {
+            sources.emplace(InputDeviceUsageSource::JOYSTICK);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_ROTARY_ENCODER)) {
+            sources.emplace(InputDeviceUsageSource::ROTARY_ENCODER);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TRACKBALL)) {
+            sources.emplace(InputDeviceUsageSource::TRACKBALL);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+            sources.emplace(InputDeviceUsageSource::TOUCHSCREEN);
+            continue;
+        }
+        sources.emplace(InputDeviceUsageSource::UNKNOWN);
+    }
+
+    return sources;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsSource.h b/services/inputflinger/InputDeviceMetricsSource.h
new file mode 100644
index 0000000..a6be8f4
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsSource.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "NotifyArgs.h"
+
+#include <linux/input.h>
+#include <statslog.h>
+
+namespace android {
+
+/**
+ * Enum representation of the InputDeviceUsageSource.
+ */
+enum class InputDeviceUsageSource : int32_t {
+    UNKNOWN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__UNKNOWN,
+    BUTTONS = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__BUTTONS,
+    KEYBOARD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__KEYBOARD,
+    DPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__DPAD,
+    GAMEPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__GAMEPAD,
+    JOYSTICK = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__JOYSTICK,
+    MOUSE = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE,
+    MOUSE_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE_CAPTURED,
+    TOUCHPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD,
+    TOUCHPAD_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD_CAPTURED,
+    ROTARY_ENCODER = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__ROTARY_ENCODER,
+    STYLUS_DIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_DIRECT,
+    STYLUS_INDIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_INDIRECT,
+    STYLUS_FUSED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_FUSED,
+    TOUCH_NAVIGATION = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCH_NAVIGATION,
+    TOUCHSCREEN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHSCREEN,
+    TRACKBALL = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TRACKBALL,
+
+    ftl_first = UNKNOWN,
+    ftl_last = TRACKBALL,
+    // Used by latency fuzzer
+    kMaxValue = ftl_last
+};
+
+/** Returns the InputDeviceUsageSource that corresponds to the key event. */
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
+
+/** Returns the InputDeviceUsageSources that correspond to the motion event. */
+std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
+
+} // namespace android
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index aa55873..1f17c16 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -24,7 +24,7 @@
 
 #include <android-base/stringprintf.h>
 #include <android/log.h>
-#include <utils/Trace.h>
+#include <input/TraceTools.h>
 
 using android::base::StringPrintf;
 
@@ -61,58 +61,42 @@
 
 // --- QueuedInputListener ---
 
-static inline void traceEvent(const char* functionName, int32_t id) {
-    if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("%s(id=0x%" PRIx32 ")", functionName, id);
-        ATRACE_NAME(message.c_str());
-    }
-}
-
 QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener)
       : mInnerListener(innerListener) {}
 
 void QueuedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifyMotion(const NotifyMotionArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifySwitch(const NotifySwitchArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifySensor(const NotifySensorArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
 void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
-    traceEvent(__func__, args.id);
     mArgsQueue.emplace_back(args);
 }
 
@@ -123,4 +107,72 @@
     mArgsQueue.clear();
 }
 
+// --- TracedInputListener ---
+
+TracedInputListener::TracedInputListener(const char* name, InputListenerInterface& innerListener)
+      : mInnerListener(innerListener), mName(name) {}
+
+void TracedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifyKey(const NotifyKeyArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifyMotion(const NotifyMotionArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifySwitch(const NotifySwitchArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifySensor(const NotifySensorArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
+void TracedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+    constexpr static auto& fnName = __func__;
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+    mInnerListener.notify(args);
+}
+
 } // namespace android
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 37b3187..92c65e1 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -23,22 +23,27 @@
 #include "InputReaderFactory.h"
 #include "UnwantedInteractionBlocker.h"
 
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <android/binder_interface_utils.h>
 #include <android/sysprop/InputProperties.sysprop.h>
 #include <binder/IPCThreadState.h>
-
+#include <com_android_input_flags.h>
+#include <inputflinger_bootstrap.rs.h>
 #include <log/log.h>
-#include <unordered_map>
-
 #include <private/android_filesystem_config.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
-static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
+namespace {
+
+const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
         sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
 
-using gui::FocusRequest;
+const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
 
-static int32_t exceptionCodeFromStatusT(status_t status) {
+int32_t exceptionCodeFromStatusT(status_t status) {
     switch (status) {
         case OK:
             return binder::Status::EX_NONE;
@@ -57,26 +62,98 @@
     }
 }
 
+// Convert a binder interface into a raw pointer to an AIBinder.
+using IInputFlingerRustBootstrapCallback = aidl::com::android::server::inputflinger::
+        IInputFlingerRust::IInputFlingerRustBootstrapCallback;
+IInputFlingerRustBootstrapCallbackAIBinder* binderToPointer(
+        IInputFlingerRustBootstrapCallback& interface) {
+    ndk::SpAIBinder spAIBinder = interface.asBinder();
+    auto* ptr = spAIBinder.get();
+    AIBinder_incStrong(ptr);
+    return ptr;
+}
+
+// Create the Rust component of InputFlinger that uses AIDL interfaces as a the foreign function
+// interface (FFI). The bootstraping process for IInputFlingerRust is as follows:
+//   - Create BnInputFlingerRustBootstrapCallback in C++.
+//   - Use the cxxbridge ffi interface to call the Rust function `create_inputflinger_rust()`, and
+//     pass the callback binder object as a raw pointer.
+//   - The Rust implementation will create the implementation of IInputFlingerRust, and pass it
+//     to C++ through the callback.
+//   - After the Rust function returns, the binder interface provided to the callback will be the
+//     only strong reference to the IInputFlingerRust.
+std::shared_ptr<IInputFlingerRust> createInputFlingerRust() {
+    using namespace aidl::com::android::server::inputflinger;
+
+    class Callback : public IInputFlingerRust::BnInputFlingerRustBootstrapCallback {
+        ndk::ScopedAStatus onProvideInputFlingerRust(
+                const std::shared_ptr<IInputFlingerRust>& inputFlingerRust) override {
+            mService = inputFlingerRust;
+            return ndk::ScopedAStatus::ok();
+        }
+
+    public:
+        std::shared_ptr<IInputFlingerRust> consumeInputFlingerRust() {
+            auto service = mService;
+            mService.reset();
+            return service;
+        }
+
+    private:
+        std::shared_ptr<IInputFlingerRust> mService;
+    };
+
+    auto callback = ndk::SharedRefBase::make<Callback>();
+    create_inputflinger_rust(binderToPointer(*callback));
+    auto service = callback->consumeInputFlingerRust();
+    LOG_ALWAYS_FATAL_IF(!service,
+                        "create_inputflinger_rust did not provide the IInputFlingerRust "
+                        "implementation through the callback.");
+    return service;
+}
+
+} // namespace
+
 /**
  * The event flow is via the "InputListener" interface, as follows:
  *   InputReader
  *     -> UnwantedInteractionBlocker
+ *     -> PointerChoreographer
  *     -> InputProcessor
  *     -> InputDeviceMetricsCollector
  *     -> InputDispatcher
  */
 InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
-                           InputDispatcherPolicyInterface& dispatcherPolicy) {
+                           InputDispatcherPolicyInterface& dispatcherPolicy,
+                           PointerChoreographerPolicyInterface& choreographerPolicy) {
+    mInputFlingerRust = createInputFlingerRust();
+
     mDispatcher = createInputDispatcher(dispatcherPolicy);
+    mTracingStages.emplace_back(
+            std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher));
 
     if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
-        mCollector = std::make_unique<InputDeviceMetricsCollector>(*mDispatcher);
+        mCollector = std::make_unique<InputDeviceMetricsCollector>(*mTracingStages.back());
+        mTracingStages.emplace_back(
+                std::make_unique<TracedInputListener>("MetricsCollector", *mCollector));
     }
 
-    mProcessor = ENABLE_INPUT_DEVICE_USAGE_METRICS ? std::make_unique<InputProcessor>(*mCollector)
-                                                   : std::make_unique<InputProcessor>(*mDispatcher);
-    mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mProcessor);
-    mReader = createInputReader(readerPolicy, *mBlocker);
+    mProcessor = std::make_unique<InputProcessor>(*mTracingStages.back());
+    mTracingStages.emplace_back(
+            std::make_unique<TracedInputListener>("InputProcessor", *mProcessor));
+
+    if (ENABLE_POINTER_CHOREOGRAPHER) {
+        mChoreographer =
+                std::make_unique<PointerChoreographer>(*mTracingStages.back(), choreographerPolicy);
+        mTracingStages.emplace_back(
+                std::make_unique<TracedInputListener>("PointerChoreographer", *mChoreographer));
+    }
+
+    mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mTracingStages.back());
+    mTracingStages.emplace_back(
+            std::make_unique<TracedInputListener>("UnwantedInteractionBlocker", *mBlocker));
+
+    mReader = createInputReader(readerPolicy, *mTracingStages.back());
 }
 
 InputManager::~InputManager() {
@@ -123,6 +200,10 @@
     return *mReader;
 }
 
+PointerChoreographerInterface& InputManager::getChoreographer() {
+    return *mChoreographer;
+}
+
 InputProcessorInterface& InputManager::getProcessor() {
     return *mProcessor;
 }
@@ -147,6 +228,10 @@
     dump += '\n';
     mBlocker->dump(dump);
     dump += '\n';
+    if (ENABLE_POINTER_CHOREOGRAPHER) {
+        mChoreographer->dump(dump);
+        dump += '\n';
+    }
     mProcessor->dump(dump);
     dump += '\n';
     if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
@@ -186,11 +271,11 @@
 
     dump += " InputFlinger dump\n";
 
-    ::write(fd, dump.c_str(), dump.size());
+    TEMP_FAILURE_RETRY(::write(fd, dump.c_str(), dump.size()));
     return NO_ERROR;
 }
 
-binder::Status InputManager::setFocusedWindow(const FocusRequest& request) {
+binder::Status InputManager::setFocusedWindow(const gui::FocusRequest& request) {
     mDispatcher->setFocusedWindow(request);
     return binder::Status::ok();
 }
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 9dc285f..20b9fd5 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -23,13 +23,16 @@
 #include "InputDeviceMetricsCollector.h"
 #include "InputProcessor.h"
 #include "InputReaderBase.h"
+#include "PointerChoreographer.h"
 #include "include/UnwantedInteractionBlockerInterface.h"
 
 #include <InputDispatcherInterface.h>
 #include <InputDispatcherPolicyInterface.h>
+#include <PointerChoreographerPolicyInterface.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
 
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
 #include <android/os/BnInputFlinger.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
@@ -37,6 +40,8 @@
 
 using android::os::BnInputFlinger;
 
+using aidl::com::android::server::inputflinger::IInputFlingerRust;
+
 namespace android {
 class InputChannel;
 class InputDispatcherThread;
@@ -83,6 +88,9 @@
     /* Gets the input reader. */
     virtual InputReaderInterface& getReader() = 0;
 
+    /* Gets the PointerChoreographer. */
+    virtual PointerChoreographerInterface& getChoreographer() = 0;
+
     /* Gets the input processor. */
     virtual InputProcessorInterface& getProcessor() = 0;
 
@@ -105,12 +113,14 @@
 
 public:
     InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
-                 InputDispatcherPolicyInterface& dispatcherPolicy);
+                 InputDispatcherPolicyInterface& dispatcherPolicy,
+                 PointerChoreographerPolicyInterface& choreographerPolicy);
 
     status_t start() override;
     status_t stop() override;
 
     InputReaderInterface& getReader() override;
+    PointerChoreographerInterface& getChoreographer() override;
     InputProcessorInterface& getProcessor() override;
     InputDeviceMetricsCollectorInterface& getMetricsCollector() override;
     InputDispatcherInterface& getDispatcher() override;
@@ -127,11 +137,17 @@
 
     std::unique_ptr<UnwantedInteractionBlockerInterface> mBlocker;
 
+    std::unique_ptr<PointerChoreographerInterface> mChoreographer;
+
     std::unique_ptr<InputProcessorInterface> mProcessor;
 
     std::unique_ptr<InputDeviceMetricsCollectorInterface> mCollector;
 
     std::unique_ptr<InputDispatcherInterface> mDispatcher;
+
+    std::shared_ptr<IInputFlingerRust> mInputFlingerRust;
+
+    std::vector<std::unique_ptr<TracedInputListener>> mTracingStages;
 };
 
 } // namespace android
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
index 0fa47d1..c34cd53 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -91,8 +91,8 @@
         readTime(readTime),
         videoFrames(videoFrames) {
     for (uint32_t i = 0; i < pointerCount; i++) {
-        this->pointerProperties.push_back(pointerProperties[i]);
-        this->pointerCoords.push_back(pointerCoords[i]);
+        this->pointerProperties.emplace_back(pointerProperties[i]);
+        this->pointerCoords.emplace_back(pointerCoords[i]);
     }
 }
 
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
new file mode 100644
index 0000000..e411abb
--- /dev/null
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PointerChoreographer"
+
+#include "PointerChoreographer.h"
+
+namespace android {
+
+// --- PointerChoreographer ---
+
+PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
+                                           PointerChoreographerPolicyInterface& policy)
+      : mNextListener(listener) {}
+
+void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifySensor(const NotifySensorArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyPointerCaptureChanged(
+        const NotifyPointerCaptureChangedArgs& args) {
+    mNextListener.notify(args);
+}
+
+void PointerChoreographer::dump(std::string& dump) {
+    dump += "PointerChoreographer:\n";
+}
+
+} // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
new file mode 100644
index 0000000..5e5f782
--- /dev/null
+++ b/services/inputflinger/PointerChoreographer.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "InputListener.h"
+#include "NotifyArgs.h"
+#include "PointerChoreographerPolicyInterface.h"
+
+namespace android {
+
+/**
+ * PointerChoreographer manages the icons shown by the system for input interactions.
+ * This includes showing the mouse cursor, stylus hover icons, and touch spots.
+ * It is responsible for accumulating the location of the mouse cursor, and populating
+ * the cursor position for incoming events, if necessary.
+ */
+class PointerChoreographerInterface : public InputListenerInterface {
+public:
+    /**
+     * This method may be called on any thread (usually by the input manager on a binder thread).
+     */
+    virtual void dump(std::string& dump) = 0;
+};
+
+class PointerChoreographer : public PointerChoreographerInterface {
+public:
+    explicit PointerChoreographer(InputListenerInterface& listener,
+                                  PointerChoreographerPolicyInterface&);
+    ~PointerChoreographer() override = default;
+
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    void notifyKey(const NotifyKeyArgs& args) override;
+    void notifyMotion(const NotifyMotionArgs& args) override;
+    void notifySwitch(const NotifySwitchArgs& args) override;
+    void notifySensor(const NotifySensorArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
+
+    void dump(std::string& dump) override;
+
+private:
+    InputListenerInterface& mNextListener;
+};
+
+} // namespace android
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index cdc4c08..6f092a6 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsWindowManagerDeviceTestCases",
+      "name": "CtsWindowManagerDeviceWindow",
       "options": [
         {
-          "include-filter": "android.server.wm.WindowInputTests"
+          "include-filter": "android.server.wm.window.WindowInputTests"
         }
       ]
     },
@@ -39,6 +39,9 @@
       "options": [
         {
           "include-filter": "android.hardware.input.cts.tests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     },
@@ -72,9 +75,6 @@
           "include-filter": "android.view.cts.TouchDelegateTest"
         },
         {
-          "include-filter": "android.view.cts.VelocityTrackerTest"
-        },
-        {
           "include-filter": "android.view.cts.VerifyInputEventTest"
         },
         {
@@ -116,6 +116,14 @@
       ]
     },
     {
+      "name": "CtsAppTestCases",
+      "options": [
+        {
+          "include-filter": "android.app.cts.ToolbarActionBarTest"
+        }
+      ]
+    },
+    {
       "name": "FrameworksServicesTests",
       "options": [
         {
@@ -142,10 +150,10 @@
   ],
   "hwasan-postsubmit": [
     {
-      "name": "CtsWindowManagerDeviceTestCases",
+      "name": "CtsWindowManagerDeviceWindow",
       "options": [
         {
-          "include-filter": "android.server.wm.WindowInputTests"
+          "include-filter": "android.server.wm.window.WindowInputTests"
         }
       ]
     },
@@ -210,9 +218,6 @@
           "include-filter": "android.view.cts.TouchDelegateTest"
         },
         {
-          "include-filter": "android.view.cts.VelocityTrackerTest"
-        },
-        {
           "include-filter": "android.view.cts.VerifyInputEventTest"
         },
         {
@@ -246,6 +251,14 @@
       ]
     },
     {
+      "name": "CtsAppTestCases",
+      "options": [
+        {
+          "include-filter": "android.app.cts.ToolbarActionBarTest"
+        }
+      ]
+    },
+    {
       "name": "FrameworksServicesTests",
       "options": [
         {
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index f889de5..0f62324 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -18,6 +18,7 @@
 #include "UnwantedInteractionBlocker.h"
 
 #include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <inttypes.h>
@@ -28,6 +29,8 @@
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
 #include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
 
+namespace input_flags = com::android::input::flags;
+
 using android::base::StringPrintf;
 
 /**
@@ -344,10 +347,14 @@
     ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
     { // acquire lock
         std::scoped_lock lock(mLock);
-        const std::vector<NotifyMotionArgs> processedArgs =
-                mPreferStylusOverTouchBlocker.processMotion(args);
-        for (const NotifyMotionArgs& loopArgs : processedArgs) {
-            notifyMotionLocked(loopArgs);
+        if (input_flags::enable_multi_device_input()) {
+            notifyMotionLocked(args);
+        } else {
+            const std::vector<NotifyMotionArgs> processedArgs =
+                    mPreferStylusOverTouchBlocker.processMotion(args);
+            for (const NotifyMotionArgs& loopArgs : processedArgs) {
+                notifyMotionLocked(loopArgs);
+            }
         }
     } // release lock
 
diff --git a/services/inputflinger/aidl/Android.bp b/services/inputflinger/aidl/Android.bp
new file mode 100644
index 0000000..314c433
--- /dev/null
+++ b/services/inputflinger/aidl/Android.bp
@@ -0,0 +1,31 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+aidl_interface {
+    name: "com.android.server.inputflinger",
+    srcs: ["**/*.aidl"],
+    unstable: true,
+    host_supported: true,
+    backend: {
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            enabled: false,
+        },
+        rust: {
+            enabled: true,
+        },
+    },
+}
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
new file mode 100644
index 0000000..8e826fd
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/**
+ * A local AIDL interface used as a foreign function interface (ffi) to
+ * communicate with the Rust component of inputflinger.
+ *
+ * NOTE: Since we use this as a local interface, all processing happens on the
+ * calling thread.
+ */
+interface IInputFlingerRust {
+
+   /**
+    * An interface used to get a strong reference to IInputFlingerRust on boot.
+    */
+    interface IInputFlingerRustBootstrapCallback {
+        void onProvideInputFlingerRust(in IInputFlingerRust inputFlingerRust);
+    }
+}
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 4e2a6fb..e200f8b 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -19,6 +19,7 @@
     shared_libs: [
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcrypto",
         "libcutils",
         "libinputflinger_base",
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index b2e274d..188d5f0 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -82,8 +82,6 @@
 
     void notifyVibratorState(int32_t deviceId, bool isOn) override {}
 
-    InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; }
-
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
         return true; // dispatch event normally
     }
@@ -187,10 +185,7 @@
         mInfo.token = mClientChannel->getConnectionToken();
         mInfo.name = "FakeWindowHandle";
         mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.frameLeft = mFrame.left;
-        mInfo.frameTop = mFrame.top;
-        mInfo.frameRight = mFrame.right;
-        mInfo.frameBottom = mFrame.bottom;
+        mInfo.frame = mFrame;
         mInfo.globalScaleFactor = 1.0;
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(mFrame);
@@ -270,7 +265,7 @@
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
 
-    dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     NotifyMotionArgs motionArgs = generateMotionArgs();
 
@@ -305,7 +300,7 @@
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
 
-    dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     for (auto _ : state) {
         MotionEvent event = generateMotionEvent();
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 492551e..8b57730 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -59,6 +59,7 @@
         "libbase",
         "libcrypto",
         "libcutils",
+        "libinput",
         "libkll",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -74,14 +75,12 @@
         android: {
             shared_libs: [
                 "libgui",
-                "libinput",
                 "libstatspull",
                 "libstatssocket",
             ],
         },
         host: {
             static_libs: [
-                "libinput",
                 "libstatspull",
                 "libstatssocket",
             ],
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index ed95de7..f304712 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -38,13 +38,4 @@
     return "?";
 }
 
-std::deque<DispatchEntry*>::iterator Connection::findWaitQueueEntry(uint32_t seq) {
-    for (std::deque<DispatchEntry*>::iterator it = waitQueue.begin(); it != waitQueue.end(); it++) {
-        if ((*it)->seq == seq) {
-            return it;
-        }
-    }
-    return waitQueue.end();
-}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index 2929d61..c17baea 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -53,11 +53,11 @@
     bool responsive = true;
 
     // Queue of events that need to be published to the connection.
-    std::deque<DispatchEntry*> outboundQueue;
+    std::deque<std::unique_ptr<DispatchEntry>> outboundQueue;
 
     // Queue of events that have been published to the connection but that have not
     // yet received a "finished" response from the application.
-    std::deque<DispatchEntry*> waitQueue;
+    std::deque<std::unique_ptr<DispatchEntry>> waitQueue;
 
     Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
                const IdGenerator& idGenerator);
@@ -65,8 +65,6 @@
     inline const std::string getInputChannelName() const { return inputChannel->getName(); }
 
     const std::string getWindowName() const;
-
-    std::deque<DispatchEntry*>::iterator findWaitQueueEntry(uint32_t seq);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index 7a41d68..c7d98ab 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -19,6 +19,9 @@
 #define LOG_TAG "InputDispatcher"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
+
+namespace input_flags = com::android::input::flags;
 
 namespace android::inputdispatcher {
 
@@ -102,7 +105,7 @@
  * Crash if a bad stream from InputListener is detected.
  * Enable this via "adb shell setprop log.tag.InputDispatcherVerifyEvents DEBUG" (requires restart)
  */
-const bool DEBUG_VERIFY_EVENTS =
+const bool DEBUG_VERIFY_EVENTS = input_flags::enable_inbound_event_verification() ||
         android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "VerifyEvents");
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index a670ebe..30e6802 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -215,8 +215,8 @@
                          int32_t buttonState, MotionClassification classification,
                          int32_t edgeFlags, float xPrecision, float yPrecision,
                          float xCursorPosition, float yCursorPosition, nsecs_t downTime,
-                         uint32_t pointerCount, const PointerProperties* pointerProperties,
-                         const PointerCoords* pointerCoords)
+                         const std::vector<PointerProperties>& pointerProperties,
+                         const std::vector<PointerCoords>& pointerCoords)
       : EventEntry(id, Type::MOTION, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
@@ -233,12 +233,8 @@
         xCursorPosition(xCursorPosition),
         yCursorPosition(yCursorPosition),
         downTime(downTime),
-        pointerCount(pointerCount) {
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        this->pointerProperties[i].copyFrom(pointerProperties[i]);
-        this->pointerCoords[i].copyFrom(pointerCoords[i]);
-    }
-}
+        pointerProperties(pointerProperties),
+        pointerCoords(pointerCoords) {}
 
 MotionEntry::~MotionEntry() {}
 
@@ -258,7 +254,7 @@
                         buttonState, motionClassificationToString(classification), edgeFlags,
                         xPrecision, yPrecision, xCursorPosition, yCursorPosition);
 
-    for (uint32_t i = 0; i < pointerCount; i++) {
+    for (uint32_t i = 0; i < getPointerCount(); i++) {
         if (i) {
             msg += ", ";
         }
@@ -269,6 +265,11 @@
     return msg;
 }
 
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry) {
+    out << motionEntry.getDescription();
+    return out;
+}
+
 // --- SensorEntry ---
 
 SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
@@ -321,7 +322,28 @@
         globalScaleFactor(globalScaleFactor),
         deliveryTime(0),
         resolvedAction(0),
-        resolvedFlags(0) {}
+        resolvedFlags(0) {
+    switch (this->eventEntry->type) {
+        case EventEntry::Type::KEY: {
+            const KeyEntry& keyEntry = static_cast<KeyEntry&>(*this->eventEntry);
+            resolvedEventId = keyEntry.id;
+            resolvedAction = keyEntry.action;
+            resolvedFlags = keyEntry.flags;
+
+            break;
+        }
+        case EventEntry::Type::MOTION: {
+            const MotionEntry& motionEntry = static_cast<MotionEntry&>(*this->eventEntry);
+            resolvedEventId = motionEntry.id;
+            resolvedAction = motionEntry.action;
+            resolvedFlags = motionEntry.flags;
+            break;
+        }
+        default: {
+            break;
+        }
+    }
+}
 
 uint32_t DispatchEntry::nextSeq() {
     // Sequence number 0 is reserved and will never be returned.
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 8dc2a2a..b341784 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -24,6 +24,7 @@
 #include <stdint.h>
 #include <utils/Timers.h>
 #include <functional>
+#include <ostream>
 #include <string>
 
 namespace android::inputdispatcher {
@@ -174,21 +175,25 @@
     float xCursorPosition;
     float yCursorPosition;
     nsecs_t downTime;
-    uint32_t pointerCount;
-    PointerProperties pointerProperties[MAX_POINTERS];
-    PointerCoords pointerCoords[MAX_POINTERS];
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    size_t getPointerCount() const { return pointerProperties.size(); }
 
     MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
                 uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
                 int32_t metaState, int32_t buttonState, MotionClassification classification,
                 int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
-                float yCursorPosition, nsecs_t downTime, uint32_t pointerCount,
-                const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
+                float yCursorPosition, nsecs_t downTime,
+                const std::vector<PointerProperties>& pointerProperties,
+                const std::vector<PointerCoords>& pointerCoords);
     std::string getDescription() const override;
 
     ~MotionEntry() override;
 };
 
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry);
+
 struct SensorEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
@@ -223,7 +228,7 @@
     const uint32_t seq; // unique sequence number, never 0
 
     std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
-    ftl::Flags<InputTarget::Flags> targetFlags;
+    const ftl::Flags<InputTarget::Flags> targetFlags;
     ui::Transform transform;
     ui::Transform rawTransform;
     float globalScaleFactor;
@@ -241,6 +246,8 @@
     DispatchEntry(std::shared_ptr<EventEntry> eventEntry,
                   ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
                   const ui::Transform& rawTransform, float globalScaleFactor);
+    DispatchEntry(const DispatchEntry&) = delete;
+    DispatchEntry& operator=(const DispatchEntry&) = delete;
 
     inline bool hasForegroundTarget() const {
         return targetFlags.test(InputTarget::Flags::FOREGROUND);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 6c211cc..7dfbf94 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -25,6 +25,7 @@
 #include <android-base/stringprintf.h>
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <log/log_event_list.h>
 #if defined(__ANDROID__)
@@ -32,6 +33,7 @@
 #endif
 #include <input/InputDevice.h>
 #include <input/PrintTools.h>
+#include <input/TraceTools.h>
 #include <openssl/mem.h>
 #include <powermanager/PowerManager.h>
 #include <unistd.h>
@@ -45,6 +47,8 @@
 #include <queue>
 #include <sstream>
 
+#include "../InputDeviceMetricsSource.h"
+
 #include "Connection.h"
 #include "DebugConfig.h"
 #include "InputDispatcher.h"
@@ -66,6 +70,7 @@
 using android::gui::WindowInfoHandle;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
+namespace input_flags = com::android::input::flags;
 
 namespace android::inputdispatcher {
 
@@ -119,6 +124,10 @@
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
+bool isEmpty(const std::stringstream& ss) {
+    return ss.rdbuf()->in_avail() == 0;
+}
+
 inline const std::string binderToString(const sp<IBinder>& binder) {
     if (binder == nullptr) {
         return "<null>";
@@ -130,11 +139,6 @@
     return uid.toString();
 }
 
-inline int32_t getMotionEventActionPointerIndex(int32_t action) {
-    return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
-            AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-}
-
 Result<void> checkKeyAction(int32_t action) {
     switch (action) {
         case AKEY_EVENT_ACTION_DOWN:
@@ -271,7 +275,8 @@
     return dump;
 }
 
-std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTime) {
+std::string dumpQueue(const std::deque<std::unique_ptr<DispatchEntry>>& queue,
+                      nsecs_t currentTime) {
     constexpr size_t maxEntries = 50; // max events to print
     constexpr size_t skipBegin = maxEntries / 2;
     const size_t skipEnd = queue.size() - maxEntries / 2;
@@ -360,7 +365,7 @@
     const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
 
     std::vector<PointerCoords> pointerCoords;
-    pointerCoords.resize(motionEntry.pointerCount);
+    pointerCoords.resize(motionEntry.getPointerCount());
 
     // Use the first pointer information to normalize all other pointers. This could be any pointer
     // as long as all other pointers are normalized to the same value and the final DispatchEntry
@@ -370,7 +375,7 @@
     ui::Transform inverseFirstTransform = firstPointerTransform.inverse();
 
     // Iterate through all pointers in the event to normalize against the first.
-    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.pointerCount; pointerIndex++) {
+    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount(); pointerIndex++) {
         const PointerProperties& pointerProperties = motionEntry.pointerProperties[pointerIndex];
         uint32_t pointerId = uint32_t(pointerProperties.id);
         const ui::Transform& currTransform = inputTarget.pointerTransforms[pointerId];
@@ -394,8 +399,7 @@
                                           motionEntry.edgeFlags, motionEntry.xPrecision,
                                           motionEntry.yPrecision, motionEntry.xCursorPosition,
                                           motionEntry.yCursorPosition, motionEntry.downTime,
-                                          motionEntry.pointerCount, motionEntry.pointerProperties,
-                                          pointerCoords.data());
+                                          motionEntry.pointerProperties, pointerCoords);
 
     if (motionEntry.injectionState) {
         combinedMotionEntry->injectionState = motionEntry.injectionState;
@@ -492,8 +496,8 @@
  */
 bool isConnectionResponsive(const Connection& connection) {
     const nsecs_t currentTime = now();
-    for (const DispatchEntry* entry : connection.waitQueue) {
-        if (entry->timeoutTime < currentTime) {
+    for (const auto& dispatchEntry : connection.waitQueue) {
+        if (dispatchEntry->timeoutTime < currentTime) {
             return false;
         }
     }
@@ -602,7 +606,7 @@
         return {entry.xCursorPosition, entry.yCursorPosition};
     }
 
-    const int32_t pointerIndex = getMotionEventActionPointerIndex(entry.action);
+    const int32_t pointerIndex = MotionEvent::getActionIndex(entry.action);
     return {entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X),
             entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)};
 }
@@ -630,11 +634,10 @@
                                                     const MotionEntry& entry) {
     std::vector<TouchedWindow> out;
     const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
-    if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER &&
-        maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE &&
-        maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-        // Not a hover event - don't need to do anything
-        return out;
+
+    if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+        // ACTION_SCROLL events should not affect the hovering pointer dispatch
+        return {};
     }
 
     // We should consider all hovering pointers here. But for now, just use the first one
@@ -669,11 +672,11 @@
             // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
             if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) {
                 android::base::LogSeverity severity = android::base::LogSeverity::FATAL;
-                if (entry.flags & AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) {
+                if (!input_flags::a11y_crash_on_inconsistent_event_stream() &&
+                    entry.flags & AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) {
                     // The Accessibility injected touch exploration event stream
                     // has known inconsistencies, so log ERROR instead of
                     // crashing the device with FATAL.
-                    // TODO(b/286037469): Move a11y severity back to FATAL.
                     severity = android::base::LogSeverity::ERROR;
                 }
                 LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
@@ -714,6 +717,40 @@
     });
 }
 
+/**
+ * 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;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -1114,7 +1151,8 @@
         const auto [x, y] = resolveTouchedPosition(motionEntry);
         const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
 
-        auto [touchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
+        sp<WindowInfoHandle> touchedWindowHandle =
+                findTouchedWindowAtLocked(displayId, x, y, isStylus);
         if (touchedWindowHandle != nullptr &&
             touchedWindowHandle->getApplicationToken() !=
                     mAwaitedFocusedApplication->getApplicationToken()) {
@@ -1238,11 +1276,10 @@
     }
 }
 
-std::pair<sp<WindowInfoHandle>, std::vector<InputTarget>>
-InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus,
-                                           bool ignoreDragWindow) const {
+sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y,
+                                                                bool isStylus,
+                                                                bool ignoreDragWindow) const {
     // Traverse windows from front to back to find touched window.
-    std::vector<InputTarget> outsideTargets;
     const auto& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
@@ -1252,16 +1289,37 @@
         const WindowInfo& info = *windowHandle->getInfo();
         if (!info.isSpy() &&
             windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
-            return {windowHandle, outsideTargets};
-        }
-
-        if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
-            addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
-                                  /*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt,
-                                  outsideTargets);
+            return windowHandle;
         }
     }
-    return {nullptr, {}};
+    return nullptr;
+}
+
+std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked(
+        int32_t displayId, const sp<WindowInfoHandle>& touchedWindow, int32_t pointerId) const {
+    if (touchedWindow == nullptr) {
+        return {};
+    }
+    // Traverse windows from front to back until we encounter the touched window.
+    std::vector<InputTarget> outsideTargets;
+    const auto& windowHandles = getWindowHandlesLocked(displayId);
+    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+        if (windowHandle == touchedWindow) {
+            // Stop iterating once we found a touched window. Any WATCH_OUTSIDE_TOUCH window
+            // below the touched window will not get ACTION_OUTSIDE event.
+            return outsideTargets;
+        }
+
+        const WindowInfo& info = *windowHandle->getInfo();
+        if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
+            std::bitset<MAX_POINTER_ID + 1> pointerIds;
+            pointerIds.set(pointerId);
+            addPointerWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
+                                         pointerIds,
+                                         /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
+        }
+    }
+    return outsideTargets;
 }
 
 std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
@@ -1763,7 +1821,7 @@
     std::vector<InputTarget> inputTargets;
     addWindowTargetLocked(focusedWindow,
                           InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
-                          /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
+                          getDownTime(*entry), inputTargets);
 
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1847,7 +1905,6 @@
     // Identify targets.
     std::vector<InputTarget> inputTargets;
 
-    bool conflictingPointerActions = false;
     InputEventInjectionResult injectionResult;
     if (isPointerEvent) {
         // Pointer event.  (eg. touchscreen)
@@ -1859,8 +1916,7 @@
         }
 
         inputTargets =
-                findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
-                                               /*byref*/ injectionResult);
+                findTouchedWindowTargetsLocked(currentTime, *entry, /*byref*/ injectionResult);
         LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED &&
                             !inputTargets.empty());
     } else {
@@ -1872,7 +1928,7 @@
             addWindowTargetLocked(focusedWindow,
                                   InputTarget::Flags::FOREGROUND |
                                           InputTarget::Flags::DISPATCH_AS_IS,
-                                  /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
+                                  getDownTime(*entry), inputTargets);
         }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -1896,11 +1952,6 @@
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
 
     // Dispatch the motion.
-    if (conflictingPointerActions) {
-        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "conflicting pointer actions");
-        synthesizeCancelationEventsForAllConnectionsLocked(options);
-    }
     dispatchEventLocked(currentTime, entry, inputTargets);
     return true;
 }
@@ -1941,7 +1992,7 @@
               entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
               entry.yPrecision, entry.downTime);
 
-        for (uint32_t i = 0; i < entry.pointerCount; i++) {
+        for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, "
                   "x=%f, y=%f, pressure=%f, size=%f, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
@@ -2199,42 +2250,8 @@
     return responsiveMonitors;
 }
 
-/**
- * In general, touch should be always split between windows. Some exceptions:
- * 1. Don't split touch is if we have an active pointer down, and a new pointer is going down that's
- *    from the same device, *and* the window that's receiving the current pointer does not support
- *    split touch.
- * 2. Don't split mouse events
- */
-bool InputDispatcher::shouldSplitTouch(const TouchState& touchState,
-                                       const MotionEntry& entry) const {
-    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;
-}
-
 std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
-        nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+        nsecs_t currentTime, const MotionEntry& entry,
         InputEventInjectionResult& outInjectionResult) {
     ATRACE_CALL();
 
@@ -2259,20 +2276,13 @@
     }
 
     bool isSplit = shouldSplitTouch(tempTouchState, entry);
-    bool switchedDevice = false;
-    if (oldState != nullptr) {
-        std::set<int32_t> oldActiveDevices = oldState->getActiveDeviceIds();
-        const bool anotherDeviceIsActive =
-                oldActiveDevices.count(entry.deviceId) == 0 && !oldActiveDevices.empty();
-        switchedDevice |= anotherDeviceIsActive;
-    }
 
     const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
     // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any
     // touchable windows.
-    const bool wasDown = oldState != nullptr && oldState->isDown();
+    const bool wasDown = oldState != nullptr && oldState->isDown(entry.deviceId);
     const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
             (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
     const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
@@ -2280,48 +2290,34 @@
             maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
 
-    // If pointers are already down, let's finish the current gesture and ignore the new events
-    // from another device. However, if the new event is a down event, let's cancel the current
-    // touch and let the new one take over.
-    if (switchedDevice && wasDown && !isDown) {
-        LOG(INFO) << "Dropping event because a pointer for another device "
-                  << " is already down in display " << displayId << ": " << entry.getDescription();
-        // TODO(b/211379801): test multiple simultaneous input streams.
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {}; // wrong device
+    if (newGesture) {
+        isSplit = false;
     }
 
-    if (newGesture) {
-        // If a new gesture is starting, clear the touch state completely.
-        tempTouchState.reset();
-        isSplit = false;
-    } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
-        ALOGI("Dropping move event because a pointer for a different device is already active "
-              "in display %" PRId32,
-              displayId);
-        // TODO(b/211379801): test multiple simultaneous input streams.
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {}; // wrong device
+    if (isDown && tempTouchState.hasHoveringPointers(entry.deviceId)) {
+        // Compatibility behaviour: ACTION_DOWN causes HOVER_EXIT to get generated.
+        tempTouchState.clearHoveringPointers(entry.deviceId);
     }
 
     if (isHoverAction) {
         // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
         // all of the existing hovering pointers and recompute.
-        tempTouchState.clearHoveringPointers();
+        tempTouchState.clearHoveringPointers(entry.deviceId);
     }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
         const auto [x, y] = resolveTouchedPosition(entry);
-        const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+        const int32_t pointerIndex = MotionEvent::getActionIndex(action);
+        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
         // Outside targets should be added upon first dispatched DOWN event. That means, this should
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
-        auto [newTouchedWindowHandle, outsideTargets] =
+        sp<WindowInfoHandle> newTouchedWindowHandle =
                 findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
         if (isDown) {
-            targets += outsideTargets;
+            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
         }
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
@@ -2346,6 +2342,8 @@
             } 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 {
@@ -2375,9 +2373,7 @@
                 continue;
             }
 
-            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-                maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                const int32_t pointerId = entry.pointerProperties[0].id;
+            if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
                 tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
             }
@@ -2400,31 +2396,24 @@
             }
 
             // Update the temporary touch state.
-            std::bitset<MAX_POINTER_ID + 1> pointerIds;
+
             if (!isHoverAction) {
-                pointerIds.set(entry.pointerProperties[pointerIndex].id);
-            }
-
-            const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
-                    maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-
-            // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would
-            // still add a window to the touch state. We should avoid doing that, but some of the
-            // later checks ("at least one foreground window") rely on this in order to dispatch
-            // the event properly, so that needs to be updated, possibly by looking at InputTargets.
-            tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, pointerIds,
-                                             isDownOrPointerDown
-                                                     ? std::make_optional(entry.eventTime)
-                                                     : std::nullopt);
-
-            // If this is the pointer going down and the touched window has a wallpaper
-            // then also add the touched wallpaper windows so they are locked in for the duration
-            // of the touch gesture.
-            // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
-            // engine only supports touch events.  We would need to add a mechanism similar
-            // to View.onGenericMotionEvent to enable wallpapers to handle these events.
-            if (isDownOrPointerDown) {
-                if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+                std::bitset<MAX_POINTER_ID + 1> pointerIds;
+                pointerIds.set(pointerId);
+                const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+                        maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+                tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId,
+                                                 pointerIds,
+                                                 isDownOrPointerDown
+                                                         ? std::make_optional(entry.eventTime)
+                                                         : std::nullopt);
+                // If this is the pointer going down and the touched window has a wallpaper
+                // then also add the touched wallpaper windows so they are locked in for the
+                // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
+                // SCROLL because the wallpaper engine only supports touch events.  We would need to
+                // add a mechanism similar to View.onGenericMotionEvent to enable wallpapers to
+                // handle these events.
+                if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) &&
                     windowHandle->getInfo()->inputConfig.test(
                             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                     sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
@@ -2446,7 +2435,6 @@
         // If a window is already pilfering some pointers, give it this new pointer as well and
         // make it pilfering. This will prevent other non-spy windows from getting this pointer,
         // which is a specific behaviour that we want.
-        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
         for (TouchedWindow& touchedWindow : tempTouchState.windows) {
             if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
                 touchedWindow.hasPilferingPointers(entry.deviceId)) {
@@ -2463,8 +2451,10 @@
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
 
         // If the pointer is not currently down, then ignore the event.
-        if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-            LOG(INFO) << "Dropping event because the pointer is not down or we previously "
+        if (!tempTouchState.isDown(entry.deviceId) &&
+            maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+            LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId
+                      << " is not down or we previously "
                          "dropped the pointer down event in display "
                       << displayId << ": " << entry.getDescription();
             outInjectionResult = InputEventInjectionResult::FAILED;
@@ -2488,14 +2478,15 @@
         addDragEventLocked(entry);
 
         // Check whether touches should slip outside of the current foreground window.
-        if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
+        if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.getPointerCount() == 1 &&
             tempTouchState.isSlippery()) {
             const auto [x, y] = resolveTouchedPosition(entry);
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
-            auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
+            sp<WindowInfoHandle> newTouchedWindowHandle =
+                    findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
             // Verify targeted injection.
             if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
@@ -2512,7 +2503,7 @@
 
             if (newTouchedWindowHandle != nullptr &&
                 !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
-                ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32,
+                ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32,
                       oldTouchedWindowHandle->getName().c_str(),
                       newTouchedWindowHandle->getName().c_str(), displayId);
 
@@ -2523,9 +2514,11 @@
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
-                addWindowTargetLocked(oldTouchedWindowHandle,
-                                      InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds,
-                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
+                addPointerWindowTargetLocked(oldTouchedWindowHandle,
+                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+                                             pointerIds,
+                                             touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                             targets);
 
                 // Make a slippery entrance into the new window.
                 if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
@@ -2560,15 +2553,16 @@
         // Update the pointerIds for non-splittable when it received pointer down.
         if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
             // If no split, we suppose all touched windows should receive pointer down.
-            const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+            const int32_t pointerIndex = MotionEvent::getActionIndex(action);
             for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
                 TouchedWindow& touchedWindow = tempTouchState.windows[i];
                 // Ignore drag window for it should just track one pointer.
                 if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
                     continue;
                 }
-                touchedWindow.addTouchingPointer(entry.deviceId,
-                                                 entry.pointerProperties[pointerIndex].id);
+                std::bitset<MAX_POINTER_ID + 1> touchingPointers;
+                touchingPointers.set(entry.pointerProperties[pointerIndex].id);
+                touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers);
             }
         }
     }
@@ -2637,16 +2631,14 @@
 
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        if (!touchedWindow.hasTouchingPointers(entry.deviceId) &&
-            !touchedWindow.hasHoveringPointers(entry.deviceId)) {
-            // Windows with hovering pointers are getting persisted inside TouchState.
-            // Do not send this event to those windows.
+        std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+                touchedWindow.getTouchingPointers(entry.deviceId);
+        if (touchingPointers.none()) {
             continue;
         }
-
-        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
-                              touchedWindow.getTouchingPointers(entry.deviceId),
-                              touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
+        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+                                     touchingPointers,
+                                     touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
     // During targeted injection, only allow owned targets to receive events
@@ -2679,54 +2671,35 @@
     }
 
     outInjectionResult = InputEventInjectionResult::SUCCEEDED;
-    // Drop the outside or hover touch windows since we will not care about them
-    // in the next iteration.
-    tempTouchState.filterNonAsIsTouchWindows();
 
-    // Update final pieces of touch state if the injector had permission.
-    if (switchedDevice) {
-        if (DEBUG_FOCUS) {
-            ALOGD("Conflicting pointer actions: Switched to a different device.");
+    for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+        // Targets that we entered in a slippery way will now become AS-IS targets
+        if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
+            touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER);
+            touchedWindow.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS;
         }
-        *outConflictingPointerActions = true;
     }
 
+    // Update final pieces of touch state if the injector had permission.
     if (isHoverAction) {
-        // Started hovering, therefore no longer down.
-        if (oldState && oldState->isDown()) {
-            ALOGD_IF(DEBUG_FOCUS,
-                     "Conflicting pointer actions: Hover received while pointer was down.");
-            *outConflictingPointerActions = true;
-        }
-        if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-            maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+        if (oldState && oldState->isDown(entry.deviceId)) {
+            // Started hovering, but the device is already down: reject the hover event
+            LOG(ERROR) << "Got hover event " << entry.getDescription()
+                       << " but the device is already down " << oldState->dump();
+            outInjectionResult = InputEventInjectionResult::FAILED;
+            return {};
         }
     } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
         // Pointer went up.
         tempTouchState.removeTouchingPointer(entry.deviceId, entry.pointerProperties[0].id);
     } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
         // All pointers up or canceled.
-        tempTouchState.reset();
-    } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
-        // First pointer went down.
-        if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) {
-            ALOGD("Conflicting pointer actions: Down received while already down or hovering.");
-            *outConflictingPointerActions = true;
-        }
+        tempTouchState.removeAllPointersForDevice(entry.deviceId);
     } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
         // One pointer went up.
-        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
-        uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
-
-        for (size_t i = 0; i < tempTouchState.windows.size();) {
-            TouchedWindow& touchedWindow = tempTouchState.windows[i];
-            touchedWindow.removeTouchingPointer(entry.deviceId, pointerId);
-            if (!touchedWindow.hasTouchingPointers(entry.deviceId)) {
-                tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
-                continue;
-            }
-            i += 1;
-        }
+        const int32_t pointerIndex = MotionEvent::getActionIndex(action);
+        const uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
+        tempTouchState.removeTouchingPointer(entry.deviceId, pointerId);
     }
 
     // Save changes unless the action was scroll in which case the temporary touch
@@ -2752,7 +2725,7 @@
     // have an explicit reason to support it.
     constexpr bool isStylus = false;
 
-    auto [dropWindow, _] =
+    sp<WindowInfoHandle> dropWindow =
             findTouchedWindowAtLocked(displayId, x, y, isStylus, /*ignoreDragWindow=*/true);
     if (dropWindow) {
         vec2 local = dropWindow->getInfo()->transform.transform(x, y);
@@ -2777,14 +2750,14 @@
 
     // Find the pointer index by id.
     int32_t pointerIndex = 0;
-    for (; static_cast<uint32_t>(pointerIndex) < entry.pointerCount; pointerIndex++) {
+    for (; static_cast<uint32_t>(pointerIndex) < entry.getPointerCount(); pointerIndex++) {
         const PointerProperties& pointerProperties = entry.pointerProperties[pointerIndex];
         if (pointerProperties.id == mDragState->pointerId) {
             break;
         }
     }
 
-    if (uint32_t(pointerIndex) == entry.pointerCount) {
+    if (uint32_t(pointerIndex) == entry.getPointerCount()) {
         LOG_ALWAYS_FATAL("Should find a valid pointer index by id %d", mDragState->pointerId);
     }
 
@@ -2806,8 +2779,9 @@
             // until we have an explicit reason to support it.
             constexpr bool isStylus = false;
 
-            auto [hoverWindowHandle, _] = findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
-                                                                    /*ignoreDragWindow=*/true);
+            sp<WindowInfoHandle> hoverWindowHandle =
+                    findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
+                                              /*ignoreDragWindow=*/true);
             // enqueue drag exit if needed.
             if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
                 !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
@@ -2825,7 +2799,7 @@
         }
 
         case AMOTION_EVENT_ACTION_POINTER_UP:
-            if (getMotionEventActionPointerIndex(entry.action) != pointerIndex) {
+            if (MotionEvent::getActionIndex(entry.action) != pointerIndex) {
                 break;
             }
             // The drag pointer is up.
@@ -2869,7 +2843,6 @@
 
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
-                                            std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
                                             std::vector<InputTarget>& inputTargets) const {
     std::vector<InputTarget>::iterator it =
@@ -2891,8 +2864,54 @@
         it = inputTargets.end() - 1;
     }
 
-    ALOG_ASSERT(it->flags == targetFlags);
-    ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
+    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
+    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
+}
+
+void InputDispatcher::addPointerWindowTargetLocked(
+        const sp<android::gui::WindowInfoHandle>& windowHandle,
+        ftl::Flags<InputTarget::Flags> targetFlags, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+        std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const
+        REQUIRES(mLock) {
+    if (pointerIds.none()) {
+        for (const auto& target : inputTargets) {
+            LOG(INFO) << "Target: " << target;
+        }
+        LOG(FATAL) << "No pointers specified for " << windowHandle->getName();
+        return;
+    }
+    std::vector<InputTarget>::iterator it =
+            std::find_if(inputTargets.begin(), inputTargets.end(),
+                         [&windowHandle](const InputTarget& inputTarget) {
+                             return inputTarget.inputChannel->getConnectionToken() ==
+                                     windowHandle->getToken();
+                         });
+
+    // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that
+    // causes a HOVER_EXIT to be generated. That means that the same entry of ACTION_DOWN would
+    // have DISPATCH_AS_HOVER_EXIT and DISPATCH_AS_IS. And therefore, we have to create separate
+    // input targets for hovering pointers and for touching pointers.
+    // If we picked an existing input target above, but it's for HOVER_EXIT - let's use a new
+    // target instead.
+    if (it != inputTargets.end() && it->flags.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
+        // Force the code below to create a new input target
+        it = inputTargets.end();
+    }
+
+    const WindowInfo* windowInfo = windowHandle->getInfo();
+
+    if (it == inputTargets.end()) {
+        std::optional<InputTarget> target =
+                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+        if (!target) {
+            return;
+        }
+        inputTargets.push_back(*target);
+        it = inputTargets.end() - 1;
+    }
+
+    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
+    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
 
     it->addPointers(pointerIds, windowInfo->transform);
 }
@@ -2988,7 +3007,7 @@
             !haveSameApplicationToken(windowInfo, otherInfo)) {
             if (DEBUG_TOUCH_OCCLUSION) {
                 info.debugInfo.push_back(
-                        dumpWindowForTouchOcclusion(otherInfo, /* isTouchedWindow */ false));
+                        dumpWindowForTouchOcclusion(otherInfo, /*isTouchedWindow=*/false));
             }
             // canBeObscuredBy() has returned true above, which means this window is untrusted, so
             // we perform the checks below to see if the touch can be propagated or not based on the
@@ -3016,8 +3035,7 @@
         }
     }
     if (DEBUG_TOUCH_OCCLUSION) {
-        info.debugInfo.push_back(
-                dumpWindowForTouchOcclusion(windowInfo, /* isTouchedWindow */ true));
+        info.debugInfo.push_back(dumpWindowForTouchOcclusion(windowInfo, /*isTouchedWindow=*/true));
     }
     return info;
 }
@@ -3030,8 +3048,8 @@
                                 "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
                         isTouchedWindow ? "[TOUCHED] " : "", info->packageName.c_str(),
                         info->ownerUid.toString().c_str(), info->id,
-                        toString(info->touchOcclusionMode).c_str(), info->alpha, info->frameLeft,
-                        info->frameTop, info->frameRight, info->frameBottom,
+                        toString(info->touchOcclusionMode).c_str(), info->alpha, info->frame.left,
+                        info->frame.top, info->frame.right, info->frame.bottom,
                         dumpRegion(info->touchableRegion).c_str(), info->name.c_str(),
                         info->inputConfig.string().c_str(), toString(info->token != nullptr),
                         info->applicationInfo.name.c_str(),
@@ -3180,12 +3198,9 @@
                                                  const std::shared_ptr<Connection>& connection,
                                                  std::shared_ptr<EventEntry> eventEntry,
                                                  const InputTarget& inputTarget) {
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
-                             connection->getInputChannelName().c_str(), eventEntry->id);
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
+                                connection->getInputChannelName().c_str(), eventEntry->id));
     if (DEBUG_DISPATCH_CYCLE) {
         ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
               "globalScaleFactor=%f, pointerIds=%s %s",
@@ -3212,7 +3227,7 @@
                             ftl::enum_string(eventEntry->type).c_str());
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
-        if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
+        if (inputTarget.pointerIds.count() != originalMotionEntry.getPointerCount()) {
             if (!inputTarget.firstDownTimeInTarget.has_value()) {
                 logDispatchStateLocked();
                 LOG(FATAL) << "Splitting motion events requires a down time to be set for the "
@@ -3250,12 +3265,9 @@
                                                    const std::shared_ptr<Connection>& connection,
                                                    std::shared_ptr<EventEntry> eventEntry,
                                                    const InputTarget& inputTarget) {
-    if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, id=0x%" PRIx32 ")",
-                             connection->getInputChannelName().c_str(), eventEntry->id);
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, id=0x%" PRIx32 ")",
+                                connection->getInputChannelName().c_str(), eventEntry->id));
     LOG_ALWAYS_FATAL_IF(!inputTarget.flags.any(InputTarget::DISPATCH_MASK),
                         "No dispatch flags are set for %s", eventEntry->getDescription().c_str());
 
@@ -3285,12 +3297,6 @@
                                                  std::shared_ptr<EventEntry> eventEntry,
                                                  const InputTarget& inputTarget,
                                                  ftl::Flags<InputTarget::Flags> dispatchMode) {
-    if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("enqueueDispatchEntry(inputChannel=%s, dispatchMode=%s)",
-                                           connection->getInputChannelName().c_str(),
-                                           dispatchMode.string().c_str());
-        ATRACE_NAME(message.c_str());
-    }
     ftl::Flags<InputTarget::Flags> inputTargetFlags = inputTarget.flags;
     if (!inputTargetFlags.any(dispatchMode)) {
         return;
@@ -3311,10 +3317,6 @@
     switch (newEntry.type) {
         case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(newEntry);
-            dispatchEntry->resolvedEventId = keyEntry.id;
-            dispatchEntry->resolvedAction = keyEntry.action;
-            dispatchEntry->resolvedFlags = keyEntry.flags;
-
             if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
                                                  dispatchEntry->resolvedFlags)) {
                 LOG(WARNING) << "channel " << connection->getInputChannelName()
@@ -3342,7 +3344,6 @@
             } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
             } else {
-                dispatchEntry->resolvedAction = motionEntry.action;
                 dispatchEntry->resolvedEventId = motionEntry.id;
             }
             if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
@@ -3358,7 +3359,6 @@
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
             }
 
-            dispatchEntry->resolvedFlags = motionEntry.flags;
             if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED;
             }
@@ -3369,6 +3369,27 @@
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
+            // Check if we need to cancel any of the ongoing gestures. We don't support multiple
+            // devices being active at the same time in the same window, so if a new device is
+            // active, cancel the gesture from the old device.
+
+            std::unique_ptr<EventEntry> cancelEvent =
+                    connection->inputState
+                            .cancelConflictingInputStream(motionEntry,
+                                                          dispatchEntry->resolvedAction);
+            if (cancelEvent != nullptr) {
+                LOG(INFO) << "Canceling pointers for device " << motionEntry.deviceId << " in "
+                          << connection->getInputChannelName() << " with event "
+                          << cancelEvent->getDescription();
+                std::unique_ptr<DispatchEntry> cancelDispatchEntry =
+                        createDispatchEntry(inputTarget, std::move(cancelEvent),
+                                            InputTarget::Flags::DISPATCH_AS_IS);
+
+                // Send these cancel events to the queue before sending the event from the new
+                // device.
+                connection->outboundQueue.emplace_back(std::move(cancelDispatchEntry));
+            }
+
             if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
                                                     dispatchEntry->resolvedFlags)) {
                 LOG(WARNING) << "channel " << connection->getInputChannelName()
@@ -3422,7 +3443,7 @@
     }
 
     // Enqueue the dispatch entry.
-    connection->outboundQueue.push_back(dispatchEntry.release());
+    connection->outboundQueue.emplace_back(std::move(dispatchEntry));
     traceOutboundQueueLength(*connection);
 }
 
@@ -3539,14 +3560,14 @@
     const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
 
     PointerCoords scaledCoords[MAX_POINTERS];
-    const PointerCoords* usingCoords = motionEntry.pointerCoords;
+    const PointerCoords* usingCoords = motionEntry.pointerCoords.data();
 
     // Set the X and Y offset and X and Y scale depending on the input source.
     if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
         !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) {
         float globalScaleFactor = dispatchEntry.globalScaleFactor;
         if (globalScaleFactor != 1.0f) {
-            for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+            for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
                 scaledCoords[i] = motionEntry.pointerCoords[i];
                 // Don't apply window scale here since we don't want scale to affect raw
                 // coordinates. The scale will be sent back to the client and applied
@@ -3557,7 +3578,7 @@
         }
     } else if (dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS)) {
         // We don't want the dispatch target to know the coordinates
-        for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+        for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
             scaledCoords[i].clear();
         }
         usingCoords = scaledCoords;
@@ -3577,23 +3598,21 @@
                                 motionEntry.yPrecision, motionEntry.xCursorPosition,
                                 motionEntry.yCursorPosition, dispatchEntry.rawTransform,
                                 motionEntry.downTime, motionEntry.eventTime,
-                                motionEntry.pointerCount, motionEntry.pointerProperties,
+                                motionEntry.getPointerCount(), motionEntry.pointerProperties.data(),
                                 usingCoords);
 }
 
 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
                                                const std::shared_ptr<Connection>& connection) {
-    if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
-                                           connection->getInputChannelName().c_str());
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
+                                connection->getInputChannelName().c_str()));
     if (DEBUG_DISPATCH_CYCLE) {
         ALOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName().c_str());
     }
 
     while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
-        DispatchEntry* dispatchEntry = connection->outboundQueue.front();
+        std::unique_ptr<DispatchEntry>& dispatchEntry = connection->outboundQueue.front();
         dispatchEntry->deliveryTime = currentTime;
         const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
         dispatchEntry->timeoutTime = currentTime + timeout.count();
@@ -3606,8 +3625,8 @@
                 const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
                 std::array<uint8_t, 32> hmac = getSignature(keyEntry, *dispatchEntry);
                 if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                    LOG(DEBUG) << "Publishing " << *dispatchEntry << " to "
-                               << connection->getInputChannelName();
+                    LOG(INFO) << "Publishing " << *dispatchEntry << " to "
+                              << connection->getInputChannelName();
                 }
 
                 // Publish the key event.
@@ -3625,8 +3644,8 @@
 
             case EventEntry::Type::MOTION: {
                 if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                    LOG(DEBUG) << "Publishing " << *dispatchEntry << " to "
-                               << connection->getInputChannelName();
+                    LOG(INFO) << "Publishing " << *dispatchEntry << " to "
+                              << connection->getInputChannelName();
                 }
                 status = publishMotionEvent(*connection, *dispatchEntry);
                 break;
@@ -3708,14 +3727,12 @@
         }
 
         // Re-enqueue the event on the wait queue.
-        connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
-                                                    connection->outboundQueue.end(),
-                                                    dispatchEntry));
+        const nsecs_t timeoutTime = dispatchEntry->timeoutTime;
+        connection->waitQueue.emplace_back(std::move(dispatchEntry));
+        connection->outboundQueue.erase(connection->outboundQueue.begin());
         traceOutboundQueueLength(*connection);
-        connection->waitQueue.push_back(dispatchEntry);
         if (connection->responsive) {
-            mAnrTracker.insert(dispatchEntry->timeoutTime,
-                               connection->inputChannel->getConnectionToken());
+            mAnrTracker.insert(timeoutTime, connection->inputChannel->getConnectionToken());
         }
         traceWaitQueueLength(*connection);
     }
@@ -3785,8 +3802,8 @@
                                                      const std::shared_ptr<Connection>& connection,
                                                      bool notify) {
     if (DEBUG_DISPATCH_CYCLE) {
-        LOG(DEBUG) << "channel '" << connection->getInputChannelName() << "'~ " << __func__
-                   << " - notify=" << toString(notify);
+        LOG(INFO) << "channel '" << connection->getInputChannelName() << "'~ " << __func__
+                  << " - notify=" << toString(notify);
     }
 
     // Clear the dispatch queues.
@@ -3814,19 +3831,17 @@
     }
 }
 
-void InputDispatcher::drainDispatchQueue(std::deque<DispatchEntry*>& queue) {
+void InputDispatcher::drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue) {
     while (!queue.empty()) {
-        DispatchEntry* dispatchEntry = queue.front();
+        releaseDispatchEntry(std::move(queue.front()));
         queue.pop_front();
-        releaseDispatchEntry(dispatchEntry);
     }
 }
 
-void InputDispatcher::releaseDispatchEntry(DispatchEntry* dispatchEntry) {
+void InputDispatcher::releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry) {
     if (dispatchEntry->hasForegroundTarget()) {
         decrementPendingForegroundDispatches(*(dispatchEntry->eventEntry));
     }
-    delete dispatchEntry;
 }
 
 int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
@@ -3966,35 +3981,54 @@
     android_log_event_list(LOGTAG_INPUT_CANCEL)
             << connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS;
 
-    InputTarget target;
-    sp<WindowInfoHandle> windowHandle;
-    if (options.displayId) {
-        windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken(),
-                                             options.displayId.value());
-    } else {
-        windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken());
-    }
-    if (windowHandle != nullptr) {
-        const WindowInfo* windowInfo = windowHandle->getInfo();
-        target.setDefaultPointerTransform(windowInfo->transform);
-        target.globalScaleFactor = windowInfo->globalScaleFactor;
-    }
-    target.inputChannel = connection->inputChannel;
-    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
-
     const bool wasEmpty = connection->outboundQueue.empty();
+    // The target to use if we don't find a window associated with the channel.
+    const InputTarget fallbackTarget{.inputChannel = connection->inputChannel,
+                                     .flags = InputTarget::Flags::DISPATCH_AS_IS};
+    const auto& token = connection->inputChannel->getConnectionToken();
 
     for (size_t i = 0; i < cancelationEvents.size(); i++) {
         std::unique_ptr<EventEntry> cancelationEventEntry = std::move(cancelationEvents[i]);
+        std::vector<InputTarget> targets{};
+
         switch (cancelationEventEntry->type) {
             case EventEntry::Type::KEY: {
-                logOutboundKeyDetails("cancel - ",
-                                      static_cast<const KeyEntry&>(*cancelationEventEntry));
+                const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry);
+                const std::optional<int32_t> targetDisplay = keyEntry.displayId != ADISPLAY_ID_NONE
+                        ? std::make_optional(keyEntry.displayId)
+                        : std::nullopt;
+                if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+                    addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
+                                          keyEntry.downTime, targets);
+                } else {
+                    targets.emplace_back(fallbackTarget);
+                }
+                logOutboundKeyDetails("cancel - ", keyEntry);
                 break;
             }
             case EventEntry::Type::MOTION: {
-                logOutboundMotionDetails("cancel - ",
-                                         static_cast<const MotionEntry&>(*cancelationEventEntry));
+                const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry);
+                const std::optional<int32_t> targetDisplay =
+                        motionEntry.displayId != ADISPLAY_ID_NONE
+                        ? std::make_optional(motionEntry.displayId)
+                        : std::nullopt;
+                if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+                    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+                    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
+                         pointerIndex++) {
+                        pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
+                    }
+                    addPointerWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
+                                                 pointerIds, 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);
+                    }
+                }
+                logOutboundMotionDetails("cancel - ", motionEntry);
                 break;
             }
             case EventEntry::Type::FOCUS:
@@ -4014,7 +4048,8 @@
             }
         }
 
-        enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), target,
+        if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+        enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0],
                                    InputTarget::Flags::DISPATCH_AS_IS);
     }
 
@@ -4043,23 +4078,33 @@
               connection->getInputChannelName().c_str(), downEvents.size());
     }
 
-    InputTarget target;
     sp<WindowInfoHandle> windowHandle =
             getWindowHandleLocked(connection->inputChannel->getConnectionToken());
-    if (windowHandle != nullptr) {
-        const WindowInfo* windowInfo = windowHandle->getInfo();
-        target.setDefaultPointerTransform(windowInfo->transform);
-        target.globalScaleFactor = windowInfo->globalScaleFactor;
-    }
-    target.inputChannel = connection->inputChannel;
-    target.flags = targetFlags;
 
     const bool wasEmpty = connection->outboundQueue.empty();
     for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
+        std::vector<InputTarget> targets{};
         switch (downEventEntry->type) {
             case EventEntry::Type::MOTION: {
-                logOutboundMotionDetails("down - ",
-                        static_cast<const MotionEntry&>(*downEventEntry));
+                const auto& motionEntry = static_cast<const MotionEntry&>(*downEventEntry);
+                if (windowHandle != nullptr) {
+                    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+                    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
+                         pointerIndex++) {
+                        pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
+                    }
+                    addPointerWindowTargetLocked(windowHandle, targetFlags, pointerIds,
+                                                 motionEntry.downTime, targets);
+                } else {
+                    targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
+                                                     .flags = targetFlags});
+                    const auto it = mDisplayInfos.find(motionEntry.displayId);
+                    if (it != mDisplayInfos.end()) {
+                        targets.back().displayTransform = it->second.transform;
+                        targets.back().setDefaultPointerTransform(it->second.transform);
+                    }
+                }
+                logOutboundMotionDetails("down - ", motionEntry);
                 break;
             }
 
@@ -4077,7 +4122,8 @@
             }
         }
 
-        enqueueDispatchEntryLocked(connection, std::move(downEventEntry), target,
+        if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+        enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0],
                                    InputTarget::Flags::DISPATCH_AS_IS);
     }
 
@@ -4104,10 +4150,10 @@
     ALOG_ASSERT(pointerIds.any());
 
     uint32_t splitPointerIndexMap[MAX_POINTERS];
-    PointerProperties splitPointerProperties[MAX_POINTERS];
-    PointerCoords splitPointerCoords[MAX_POINTERS];
+    std::vector<PointerProperties> splitPointerProperties;
+    std::vector<PointerCoords> splitPointerCoords;
 
-    uint32_t originalPointerCount = originalMotionEntry.pointerCount;
+    uint32_t originalPointerCount = originalMotionEntry.getPointerCount();
     uint32_t splitPointerCount = 0;
 
     for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
@@ -4117,9 +4163,8 @@
         uint32_t pointerId = uint32_t(pointerProperties.id);
         if (pointerIds.test(pointerId)) {
             splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
-            splitPointerProperties[splitPointerCount].copyFrom(pointerProperties);
-            splitPointerCoords[splitPointerCount].copyFrom(
-                    originalMotionEntry.pointerCoords[originalPointerIndex]);
+            splitPointerProperties.push_back(pointerProperties);
+            splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]);
             splitPointerCount += 1;
         }
     }
@@ -4141,7 +4186,7 @@
     int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
     if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
         maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
-        int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+        int32_t originalPointerIndex = MotionEvent::getActionIndex(action);
         const PointerProperties& pointerProperties =
                 originalMotionEntry.pointerProperties[originalPointerIndex];
         uint32_t pointerId = uint32_t(pointerProperties.id);
@@ -4176,12 +4221,10 @@
     }
 
     int32_t newId = mIdGenerator.nextId();
-    if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("Split MotionEvent(id=0x%" PRIx32
-                                           ") to MotionEvent(id=0x%" PRIx32 ").",
-                                           originalMotionEntry.id, newId);
-        ATRACE_NAME(message.c_str());
-    }
+    ATRACE_NAME_IF(ATRACE_ENABLED(),
+                   StringPrintf("Split MotionEvent(id=0x%" PRIx32 ") to MotionEvent(id=0x%" PRIx32
+                                ").",
+                                originalMotionEntry.id, newId));
     std::unique_ptr<MotionEntry> splitMotionEntry =
             std::make_unique<MotionEntry>(newId, originalMotionEntry.eventTime,
                                           originalMotionEntry.deviceId, originalMotionEntry.source,
@@ -4196,8 +4239,7 @@
                                           originalMotionEntry.yPrecision,
                                           originalMotionEntry.xCursorPosition,
                                           originalMotionEntry.yCursorPosition, splitDownTime,
-                                          splitPointerCount, splitPointerProperties,
-                                          splitPointerCoords);
+                                          splitPointerProperties, splitPointerCoords);
 
     if (originalMotionEntry.injectionState) {
         splitMotionEntry->injectionState = originalMotionEntry.injectionState;
@@ -4207,6 +4249,11 @@
     return splitMotionEntry;
 }
 
+void InputDispatcher::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    std::scoped_lock _l(mLock);
+    mLatencyTracker.setInputDevices(args.inputDeviceInfos);
+}
+
 void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args.eventTime);
@@ -4226,45 +4273,6 @@
     }
 }
 
-/**
- * If one of the meta shortcuts is detected, process them here:
- *     Meta + Backspace; Meta + Grave; Meta + Left arrow -> generate BACK
- * Most System shortcuts are handled in PhoneWindowManager.java except 'Back' shortcuts. Unlike
- * Back, other shortcuts DO NOT need to be sent to applications and are fully handled by the system.
- * But for Back key and Back shortcuts, we need to send KEYCODE_BACK to applications which can
- * potentially handle the back key presses.
- * Note: We don't send any Meta based KeyEvents to applications, so we need to convert to a KeyEvent
- * where meta modifier is off before sending. Currently only use case is 'Back'.
- */
-void InputDispatcher::accelerateMetaShortcuts(const int32_t deviceId, const int32_t action,
-                                              int32_t& keyCode, int32_t& metaState) {
-    if (metaState & AMETA_META_ON && action == AKEY_EVENT_ACTION_DOWN) {
-        int32_t newKeyCode = AKEYCODE_UNKNOWN;
-        if (keyCode == AKEYCODE_DEL || keyCode == AKEYCODE_GRAVE || keyCode == AKEYCODE_DPAD_LEFT) {
-            newKeyCode = AKEYCODE_BACK;
-        }
-        if (newKeyCode != AKEYCODE_UNKNOWN) {
-            std::scoped_lock _l(mLock);
-            struct KeyReplacement replacement = {keyCode, deviceId};
-            mReplacedKeys[replacement] = newKeyCode;
-            keyCode = newKeyCode;
-            metaState &= ~(AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
-        }
-    } else if (action == AKEY_EVENT_ACTION_UP) {
-        // In order to maintain a consistent stream of up and down events, check to see if the key
-        // going up is one we've replaced in a down event and haven't yet replaced in an up event,
-        // even if the modifier was released between the down and the up events.
-        std::scoped_lock _l(mLock);
-        struct KeyReplacement replacement = {keyCode, deviceId};
-        auto replacementIt = mReplacedKeys.find(replacement);
-        if (replacementIt != mReplacedKeys.end()) {
-            keyCode = replacementIt->second;
-            mReplacedKeys.erase(replacementIt);
-            metaState &= ~(AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
-        }
-    }
-}
-
 void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
     ALOGD_IF(debugInboundEventDetails(),
              "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
@@ -4297,8 +4305,6 @@
     policyFlags |= POLICY_FLAG_TRUSTED;
 
     int32_t keyCode = args.keyCode;
-    accelerateMetaShortcuts(args.deviceId, args.action, keyCode, metaState);
-
     KeyEvent event;
     event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action,
                      flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime,
@@ -4377,7 +4383,7 @@
             validateMotionEvent(args.action, args.actionButton, args.getPointerCount(),
                                 args.pointerProperties.data());
     if (!motionCheck.ok()) {
-        LOG(ERROR) << "Invalid event: " << args.dump() << "; reason: " << motionCheck.error();
+        LOG(FATAL) << "Invalid event: " << args.dump() << "; reason: " << motionCheck.error();
         return;
     }
 
@@ -4386,9 +4392,9 @@
                 mVerifiersByDisplay.try_emplace(args.displayId,
                                                 StringPrintf("display %" PRId32, args.displayId));
         Result<void> result =
-                it->second.processMovement(args.deviceId, args.action, args.getPointerCount(),
-                                           args.pointerProperties.data(), args.pointerCoords.data(),
-                                           args.flags);
+                it->second.processMovement(args.deviceId, args.source, args.action,
+                                           args.getPointerCount(), args.pointerProperties.data(),
+                                           args.pointerCoords.data(), args.flags);
         if (!result.ok()) {
             LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
         }
@@ -4452,15 +4458,16 @@
                                               args.buttonState, args.classification, args.edgeFlags,
                                               args.xPrecision, args.yPrecision,
                                               args.xCursorPosition, args.yCursorPosition,
-                                              args.downTime, args.getPointerCount(),
-                                              args.pointerProperties.data(),
-                                              args.pointerCoords.data());
+                                              args.downTime, args.pointerProperties,
+                                              args.pointerCoords);
 
         if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
             IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
             !mInputFilterEnabled) {
             const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
-            mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime);
+            std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(args);
+            mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime,
+                                          args.deviceId, sources);
         }
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
@@ -4536,6 +4543,10 @@
         std::unique_ptr<DeviceResetEntry> newEntry =
                 std::make_unique<DeviceResetEntry>(args.id, args.eventTime, args.deviceId);
         needWake = enqueueInboundEventLocked(std::move(newEntry));
+
+        for (auto& [_, verifier] : mVerifiersByDisplay) {
+            verifier.resetDevice(args.deviceId);
+        }
     } // release lock
 
     if (needWake) {
@@ -4574,10 +4585,10 @@
     }
 
     if (debugInboundEventDetails()) {
-        LOG(DEBUG) << __func__ << ": targetUid=" << toString(targetUid, &uidString)
-                   << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count()
-                   << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec
-                   << ", event=" << *event;
+        LOG(INFO) << __func__ << ": targetUid=" << toString(targetUid, &uidString)
+                  << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count()
+                  << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec
+                  << ", event=" << *event;
     }
     nsecs_t endTime = now() + std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
 
@@ -4589,7 +4600,7 @@
     // the injected event, it is responsible for setting POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY.
     // For those events, we will set FLAG_IS_ACCESSIBILITY_EVENT to allow apps to distinguish them
     // from events that originate from actual hardware.
-    int32_t resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
+    DeviceId resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
     if (policyFlags & POLICY_FLAG_FILTERED) {
         resolvedDeviceId = event->getDeviceId();
     }
@@ -4605,8 +4616,6 @@
             }
             int32_t keyCode = incomingKey.getKeyCode();
             int32_t metaState = incomingKey.getMetaState();
-            accelerateMetaShortcuts(resolvedDeviceId, action,
-                                    /*byref*/ keyCode, /*byref*/ metaState);
             KeyEvent keyEvent;
             keyEvent.initialize(incomingKey.getId(), resolvedDeviceId, incomingKey.getSource(),
                                 incomingKey.getDisplayId(), INVALID_HMAC, action, flags, keyCode,
@@ -4664,6 +4673,11 @@
 
             mLock.lock();
             const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
+            const size_t pointerCount = motionEvent.getPointerCount();
+            const std::vector<PointerProperties>
+                    pointerProperties(motionEvent.getPointerProperties(),
+                                      motionEvent.getPointerProperties() + pointerCount);
+
             const PointerCoords* samplePointerCoords = motionEvent.getSamplePointerCoords();
             std::unique_ptr<MotionEntry> injectedEntry =
                     std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
@@ -4678,33 +4692,28 @@
                                                   motionEvent.getYPrecision(),
                                                   motionEvent.getRawXCursorPosition(),
                                                   motionEvent.getRawYCursorPosition(),
-                                                  motionEvent.getDownTime(),
-                                                  motionEvent.getPointerCount(),
-                                                  motionEvent.getPointerProperties(),
-                                                  samplePointerCoords);
+                                                  motionEvent.getDownTime(), pointerProperties,
+                                                  std::vector<PointerCoords>(samplePointerCoords,
+                                                                             samplePointerCoords +
+                                                                                     pointerCount));
             transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform());
             injectedEntries.push(std::move(injectedEntry));
             for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
                 sampleEventTimes += 1;
                 samplePointerCoords += motionEvent.getPointerCount();
-                std::unique_ptr<MotionEntry> nextInjectedEntry =
-                        std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
-                                                      resolvedDeviceId, motionEvent.getSource(),
-                                                      displayId, policyFlags,
-                                                      motionEvent.getAction(),
-                                                      motionEvent.getActionButton(), flags,
-                                                      motionEvent.getMetaState(),
-                                                      motionEvent.getButtonState(),
-                                                      motionEvent.getClassification(),
-                                                      motionEvent.getEdgeFlags(),
-                                                      motionEvent.getXPrecision(),
-                                                      motionEvent.getYPrecision(),
-                                                      motionEvent.getRawXCursorPosition(),
-                                                      motionEvent.getRawYCursorPosition(),
-                                                      motionEvent.getDownTime(),
-                                                      motionEvent.getPointerCount(),
-                                                      motionEvent.getPointerProperties(),
-                                                      samplePointerCoords);
+                std::unique_ptr<MotionEntry> nextInjectedEntry = std::make_unique<
+                        MotionEntry>(motionEvent.getId(), *sampleEventTimes, resolvedDeviceId,
+                                     motionEvent.getSource(), displayId, policyFlags,
+                                     motionEvent.getAction(), motionEvent.getActionButton(), flags,
+                                     motionEvent.getMetaState(), motionEvent.getButtonState(),
+                                     motionEvent.getClassification(), motionEvent.getEdgeFlags(),
+                                     motionEvent.getXPrecision(), motionEvent.getYPrecision(),
+                                     motionEvent.getRawXCursorPosition(),
+                                     motionEvent.getRawYCursorPosition(), motionEvent.getDownTime(),
+                                     pointerProperties,
+                                     std::vector<PointerCoords>(samplePointerCoords,
+                                                                samplePointerCoords +
+                                                                        pointerCount));
                 transformMotionEntryForInjectionLocked(*nextInjectedEntry,
                                                        motionEvent.getTransform());
                 injectedEntries.push(std::move(nextInjectedEntry));
@@ -4728,7 +4737,7 @@
     bool needWake = false;
     while (!injectedEntries.empty()) {
         if (DEBUG_INJECTION) {
-            LOG(DEBUG) << "Injecting " << injectedEntries.front()->getDescription();
+            LOG(INFO) << "Injecting " << injectedEntries.front()->getDescription();
         }
         needWake |= enqueueInboundEventLocked(std::move(injectedEntries.front()));
         injectedEntries.pop();
@@ -4792,8 +4801,8 @@
     } // release lock
 
     if (DEBUG_INJECTION) {
-        LOG(DEBUG) << "injectInputEvent - Finished with result "
-                   << ftl::enum_string(injectionResult);
+        LOG(INFO) << "injectInputEvent - Finished with result "
+                  << ftl::enum_string(injectionResult);
     }
 
     return injectionResult;
@@ -4837,8 +4846,8 @@
     InjectionState* injectionState = entry.injectionState;
     if (injectionState) {
         if (DEBUG_INJECTION) {
-            LOG(DEBUG) << "Setting input event injection result to "
-                       << ftl::enum_string(injectionResult);
+            LOG(INFO) << "Setting input event injection result to "
+                      << ftl::enum_string(injectionResult);
         }
 
         if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
@@ -4883,7 +4892,7 @@
         entry.xCursorPosition = cursor.x;
         entry.yCursorPosition = cursor.y;
     }
-    for (uint32_t i = 0; i < entry.pointerCount; i++) {
+    for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
         entry.pointerCoords[i] =
                 MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
                                                         entry.pointerCoords[i]);
@@ -4916,29 +4925,26 @@
 }
 
 sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
-        const sp<IBinder>& windowHandleToken) const {
+        const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId) const {
     if (windowHandleToken == nullptr) {
         return nullptr;
     }
 
-    for (auto& it : mWindowHandlesByDisplay) {
-        const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
-        for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
-            if (windowHandle->getToken() == windowHandleToken) {
-                return windowHandle;
+    if (!displayId) {
+        // Look through all displays.
+        for (auto& it : mWindowHandlesByDisplay) {
+            const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+            for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+                if (windowHandle->getToken() == windowHandleToken) {
+                    return windowHandle;
+                }
             }
         }
-    }
-    return nullptr;
-}
-
-sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(const sp<IBinder>& windowHandleToken,
-                                                            int displayId) const {
-    if (windowHandleToken == nullptr) {
         return nullptr;
     }
 
-    for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
+    // Only look through the requested display.
+    for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(*displayId)) {
         if (windowHandle->getToken() == windowHandleToken) {
             return windowHandle;
         }
@@ -5093,19 +5099,6 @@
     mWindowHandlesByDisplay[displayId] = newHandles;
 }
 
-void InputDispatcher::setInputWindows(
-        const std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>>& handlesPerDisplay) {
-    // TODO(b/198444055): Remove setInputWindows from InputDispatcher.
-    { // acquire lock
-        std::scoped_lock _l(mLock);
-        for (const auto& [displayId, handles] : handlesPerDisplay) {
-            setInputWindowsLocked(handles, displayId);
-        }
-    }
-    // Wake up poll loop since it may need to make new input dispatching choices.
-    mLooper->wake();
-}
-
 /**
  * Called from InputManagerService, update window handle list by displayId that can receive input.
  * A window handle contains information about InputChannel, Touch Region, Types, Focused,...
@@ -5153,13 +5146,6 @@
     // Copy old handles for release if they are no longer present.
     const std::vector<sp<WindowInfoHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
 
-    // Save the old windows' orientation by ID before it gets updated.
-    std::unordered_map<int32_t, uint32_t> oldWindowOrientations;
-    for (const sp<WindowInfoHandle>& handle : oldWindowHandles) {
-        oldWindowOrientations.emplace(handle->getId(),
-                                      handle->getInfo()->transform.getOrientation());
-    }
-
     updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
 
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
@@ -5177,10 +5163,8 @@
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
             if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
-                if (DEBUG_FOCUS) {
-                    ALOGD("Touched window was removed: %s in display %" PRId32,
-                          touchedWindow.windowHandle->getName().c_str(), displayId);
-                }
+                LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+                          << " in display %" << displayId;
                 std::shared_ptr<InputChannel> touchedInputChannel =
                         getInputChannelLocked(touchedWindow.windowHandle->getToken());
                 if (touchedInputChannel != nullptr) {
@@ -5213,23 +5197,6 @@
         }
     }
 
-    // Determine if the orientation of any of the input windows have changed, and cancel all
-    // pointer events if necessary.
-    for (const sp<WindowInfoHandle>& oldWindowHandle : oldWindowHandles) {
-        const sp<WindowInfoHandle> newWindowHandle = getWindowHandleLocked(oldWindowHandle);
-        if (newWindowHandle != nullptr &&
-            newWindowHandle->getInfo()->transform.getOrientation() !=
-                    oldWindowOrientations[oldWindowHandle->getId()]) {
-            std::shared_ptr<InputChannel> inputChannel =
-                    getInputChannelLocked(newWindowHandle->getToken());
-            if (inputChannel != nullptr) {
-                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                           "touched window's orientation changed");
-                synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
-            }
-        }
-    }
-
     // Release information for windows that are no longer present.
     // This ensures that unused input channels are released promptly.
     // Otherwise, they might stick around until the window handle is destroyed
@@ -5482,8 +5449,8 @@
         }
         std::set<int32_t> deviceIds = touchedWindow->getTouchingDeviceIds();
         if (deviceIds.size() != 1) {
-            LOG(DEBUG) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
-                       << " for window: " << touchedWindow->dump();
+            LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
+                      << " for window: " << touchedWindow->dump();
             return false;
         }
         const int32_t deviceId = *deviceIds.begin();
@@ -5618,7 +5585,6 @@
 
     mAnrTracker.clear();
     mTouchStatesByDisplay.clear();
-    mReplacedKeys.clear();
 }
 
 void InputDispatcher::logDispatchStateLocked() const {
@@ -5717,9 +5683,9 @@
                                          i, windowInfo->name.c_str(), windowInfo->id,
                                          windowInfo->displayId,
                                          windowInfo->inputConfig.string().c_str(),
-                                         windowInfo->alpha, windowInfo->frameLeft,
-                                         windowInfo->frameTop, windowInfo->frameRight,
-                                         windowInfo->frameBottom, windowInfo->globalScaleFactor,
+                                         windowInfo->alpha, windowInfo->frame.left,
+                                         windowInfo->frame.top, windowInfo->frame.right,
+                                         windowInfo->frame.bottom, windowInfo->globalScaleFactor,
                                          windowInfo->applicationInfo.name.c_str(),
                                          binderToString(windowInfo->applicationInfo.token).c_str());
                     dump += dumpRegion(windowInfo->touchableRegion);
@@ -5787,16 +5753,6 @@
         dump += INDENT "InboundQueue: <empty>\n";
     }
 
-    if (!mReplacedKeys.empty()) {
-        dump += INDENT "ReplacedKeys:\n";
-        for (const auto& [replacement, newKeyCode] : mReplacedKeys) {
-            dump += StringPrintf(INDENT2 "originalKeyCode=%d, deviceId=%d -> newKeyCode=%d\n",
-                                 replacement.keyCode, replacement.deviceId, newKeyCode);
-        }
-    } else {
-        dump += INDENT "ReplacedKeys: <empty>\n";
-    }
-
     if (!mCommandQueue.empty()) {
         dump += StringPrintf(INDENT "CommandQueue: size=%zu\n", mCommandQueue.size());
     } else {
@@ -5830,6 +5786,12 @@
             } else {
                 dump += INDENT3 "WaitQueue: <empty>\n";
             }
+            std::stringstream inputStateDump;
+            inputStateDump << connection->inputState;
+            if (!isEmpty(inputStateDump)) {
+                dump += INDENT3 "InputState: ";
+                dump += inputStateDump.str() + "\n";
+            }
         }
     } else {
         dump += INDENT "Connections: <none>\n";
@@ -6020,52 +5982,55 @@
 status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
     const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
     if (!requestingChannel) {
-        ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
+        LOG(WARNING)
+                << "Attempted to pilfer pointers from an un-registered channel or invalid token";
         return BAD_VALUE;
     }
 
     auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
     if (statePtr == nullptr || windowPtr == nullptr) {
-        ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
-              " Ignoring.");
+        LOG(WARNING)
+                << "Attempted to pilfer points from a channel without any on-going pointer streams."
+                   " Ignoring.";
         return BAD_VALUE;
     }
     std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds();
-    if (deviceIds.size() != 1) {
-        LOG(WARNING) << "Can't pilfer. Currently touching devices: " << dumpSet(deviceIds)
-                     << " in window: " << windowPtr->dump();
+    if (deviceIds.empty()) {
+        LOG(WARNING) << "Can't pilfer: no touching devices in window: " << windowPtr->dump();
         return BAD_VALUE;
     }
-    const int32_t deviceId = *deviceIds.begin();
 
-    TouchState& state = *statePtr;
-    TouchedWindow& window = *windowPtr;
-    // Send cancel events to all the input channels we're stealing from.
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                               "input channel stole pointer stream");
-    options.deviceId = deviceId;
-    options.displayId = displayId;
-    std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
-    options.pointerIds = pointerIds;
-    std::string canceledWindows;
-    for (const TouchedWindow& w : state.windows) {
-        const std::shared_ptr<InputChannel> channel =
-                getInputChannelLocked(w.windowHandle->getToken());
-        if (channel != nullptr && channel->getConnectionToken() != token) {
-            synthesizeCancelationEventsForInputChannelLocked(channel, options);
-            canceledWindows += canceledWindows.empty() ? "[" : ", ";
-            canceledWindows += channel->getName();
+    for (const DeviceId deviceId : deviceIds) {
+        TouchState& state = *statePtr;
+        TouchedWindow& window = *windowPtr;
+        // Send cancel events to all the input channels we're stealing from.
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                   "input channel stole pointer stream");
+        options.deviceId = deviceId;
+        options.displayId = displayId;
+        std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+        options.pointerIds = pointerIds;
+        std::string canceledWindows;
+        for (const TouchedWindow& w : state.windows) {
+            const std::shared_ptr<InputChannel> channel =
+                    getInputChannelLocked(w.windowHandle->getToken());
+            if (channel != nullptr && channel->getConnectionToken() != token) {
+                synthesizeCancelationEventsForInputChannelLocked(channel, options);
+                canceledWindows += canceledWindows.empty() ? "[" : ", ";
+                canceledWindows += channel->getName();
+            }
         }
+        canceledWindows += canceledWindows.empty() ? "[]" : "]";
+        LOG(INFO) << "Channel " << requestingChannel->getName()
+                  << " is stealing input gesture for device " << deviceId << " from "
+                  << canceledWindows;
+
+        // Prevent the gesture from being sent to any other windows.
+        // This only blocks relevant pointers to be sent to other windows
+        window.addPilferingPointers(deviceId, pointerIds);
+
+        state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
     }
-    canceledWindows += canceledWindows.empty() ? "[]" : "]";
-    ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
-          canceledWindows.c_str());
-
-    // Prevent the gesture from being sent to any other windows.
-    // This only blocks relevant pointers to be sent to other windows
-    window.addPilferingPointers(deviceId, pointerIds);
-
-    state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
     return OK;
 }
 
@@ -6163,43 +6128,52 @@
                                                      uint32_t seq, bool handled,
                                                      nsecs_t consumeTime) {
     // Handle post-event policy actions.
-    std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
-    if (dispatchEntryIt == connection->waitQueue.end()) {
-        return;
-    }
-    DispatchEntry* dispatchEntry = *dispatchEntryIt;
-    const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
-    if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
-        ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
-              ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
-    }
-    if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
-        mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
-                                           connection->inputChannel->getConnectionToken(),
-                                           dispatchEntry->deliveryTime, consumeTime, finishTime);
-    }
-
     bool restartEvent;
-    if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
-        KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
-        restartEvent =
-                afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
-    } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
-        MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
-        restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry, motionEntry,
-                                                           handled);
-    } else {
-        restartEvent = false;
-    }
+
+    { // Start critical section
+        auto dispatchEntryIt =
+                std::find_if(connection->waitQueue.begin(), connection->waitQueue.end(),
+                             [seq](auto& e) { return e->seq == seq; });
+        if (dispatchEntryIt == connection->waitQueue.end()) {
+            return;
+        }
+
+        DispatchEntry& dispatchEntry = **dispatchEntryIt;
+
+        const nsecs_t eventDuration = finishTime - dispatchEntry.deliveryTime;
+        if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+            ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+                  ns2ms(eventDuration), dispatchEntry.eventEntry->getDescription().c_str());
+        }
+        if (shouldReportFinishedEvent(dispatchEntry, *connection)) {
+            mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id,
+                                               connection->inputChannel->getConnectionToken(),
+                                               dispatchEntry.deliveryTime, consumeTime, finishTime);
+        }
+
+        if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) {
+            KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry.eventEntry));
+            restartEvent =
+                    afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
+        } else if (dispatchEntry.eventEntry->type == EventEntry::Type::MOTION) {
+            MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry.eventEntry));
+            restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry,
+                                                               motionEntry, handled);
+        } else {
+            restartEvent = false;
+        }
+    } // End critical section: The -LockedInterruptable methods may have released the lock.
 
     // Dequeue the event and start the next cycle.
     // Because the lock might have been released, it is possible that the
     // contents of the wait queue to have been drained, so we need to double-check
     // a few things.
-    dispatchEntryIt = connection->findWaitQueueEntry(seq);
-    if (dispatchEntryIt != connection->waitQueue.end()) {
-        dispatchEntry = *dispatchEntryIt;
-        connection->waitQueue.erase(dispatchEntryIt);
+    auto entryIt = std::find_if(connection->waitQueue.begin(), connection->waitQueue.end(),
+                                [seq](auto& e) { return e->seq == seq; });
+    if (entryIt != connection->waitQueue.end()) {
+        std::unique_ptr<DispatchEntry> dispatchEntry = std::move(*entryIt);
+        connection->waitQueue.erase(entryIt);
+
         const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
         mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
         if (!connection->responsive) {
@@ -6211,10 +6185,10 @@
         }
         traceWaitQueueLength(*connection);
         if (restartEvent && connection->status == Connection::Status::NORMAL) {
-            connection->outboundQueue.push_front(dispatchEntry);
+            connection->outboundQueue.emplace_front(std::move(dispatchEntry));
             traceOutboundQueueLength(*connection);
         } else {
-            releaseDispatchEntry(dispatchEntry);
+            releaseDispatchEntry(std::move(dispatchEntry));
         }
     }
 
@@ -6258,13 +6232,13 @@
      * processes the events linearly. So providing information about the oldest entry seems to be
      * most useful.
      */
-    DispatchEntry* oldestEntry = *connection->waitQueue.begin();
-    const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
+    DispatchEntry& oldestEntry = *connection->waitQueue.front();
+    const nsecs_t currentWait = now() - oldestEntry.deliveryTime;
     std::string reason =
             android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
                                         connection->inputChannel->getName().c_str(),
                                         ns2ms(currentWait),
-                                        oldestEntry->eventEntry->getDescription().c_str());
+                                        oldestEntry.eventEntry->getDescription().c_str());
     sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
     updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
 
@@ -6401,7 +6375,7 @@
 }
 
 bool InputDispatcher::afterKeyEventLockedInterruptable(
-        const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+        const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
         KeyEntry& keyEntry, bool handled) {
     if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
         if (!handled) {
@@ -6419,7 +6393,7 @@
         connection->inputState.removeFallbackKey(originalKeyCode);
     }
 
-    if (handled || !dispatchEntry->hasForegroundTarget()) {
+    if (handled || !dispatchEntry.hasForegroundTarget()) {
         // If the application handles the original key for which we previously
         // generated a fallback or if the window is not a foreground window,
         // then cancel the associated fallback key, if any.
@@ -6586,7 +6560,7 @@
 }
 
 bool InputDispatcher::afterMotionEventLockedInterruptable(
-        const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+        const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
         MotionEntry& motionEntry, bool handled) {
     return false;
 }
@@ -6688,6 +6662,7 @@
         }
     }
     if (changes.newFocus) {
+        resetNoFocusedWindowTimeoutLocked();
         enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason);
     }
 
@@ -6748,7 +6723,7 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         // Set an empty list to remove all handles from the specific display.
-        setInputWindowsLocked(/* window handles */ {}, displayId);
+        setInputWindowsLocked(/*windowInfoHandles=*/{}, displayId);
         setFocusedApplicationLocked(displayId, nullptr);
         // Call focus resolver to clean up stale requests. This must be called after input windows
         // have been removed for the removed display.
@@ -6837,13 +6812,6 @@
     mLooper->wake();
 }
 
-void InputDispatcher::requestRefreshConfiguration() {
-    InputDispatcherConfiguration config = mPolicy.getDispatcherConfiguration();
-
-    std::scoped_lock _l(mLock);
-    mConfig = config;
-}
-
 void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout) {
     std::scoped_lock _l(mLock);
     mMonitorDispatchingTimeout = timeout;
@@ -6871,10 +6839,11 @@
 
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
-        addWindowTargetLocked(oldWallpaper,
-                              oldTouchedWindow.targetFlags |
-                                      InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
-                              pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
+        addPointerWindowTargetLocked(oldWallpaper,
+                                     oldTouchedWindow.targetFlags |
+                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+                                     pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId),
+                                     targets);
         state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
     }
 
@@ -6956,4 +6925,11 @@
     return nullptr;
 }
 
+void InputDispatcher::setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
+    std::scoped_lock _l(mLock);
+
+    mConfig.keyRepeatTimeout = timeout;
+    mConfig.keyRepeatDelay = delay;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 2b8b37e..a1127a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -25,7 +25,6 @@
 #include "InputDispatcherConfiguration.h"
 #include "InputDispatcherInterface.h"
 #include "InputDispatcherPolicyInterface.h"
-#include "InputState.h"
 #include "InputTarget.h"
 #include "InputThread.h"
 #include "LatencyAggregator.h"
@@ -93,7 +92,7 @@
     status_t start() override;
     status_t stop() override;
 
-    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override{};
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
     void notifyKey(const NotifyKeyArgs& args) override;
     void notifyMotion(const NotifyMotionArgs& args) override;
@@ -110,9 +109,6 @@
 
     std::unique_ptr<VerifiedInputEvent> verifyInputEvent(const InputEvent& event) override;
 
-    void setInputWindows(
-            const std::unordered_map<int32_t, std::vector<sp<android::gui::WindowInfoHandle>>>&
-                    handlesPerDisplay) override;
     void setFocusedApplication(
             int32_t displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) override;
@@ -148,11 +144,11 @@
 
     void cancelCurrentTouch() override;
 
-    void requestRefreshConfiguration() override;
-
     // Public to allow tests to verify that a Monitor can get ANR.
     void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
+    void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) override;
+
 private:
     enum class DropReason {
         NOT_DROPPED,
@@ -242,9 +238,12 @@
     // to transfer focus to a new application.
     std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
-    std::pair<sp<android::gui::WindowInfoHandle>, std::vector<InputTarget>>
-    findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus = false,
-                              bool ignoreDragWindow = false) const REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
+            int32_t displayId, float x, float y, bool isStylus = false,
+            bool ignoreDragWindow = false) const REQUIRES(mLock);
+    std::vector<InputTarget> findOutsideTargetsLocked(
+            int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
+            int32_t pointerId) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
             int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
@@ -303,25 +302,6 @@
     void resetKeyRepeatLocked() REQUIRES(mLock);
     std::shared_ptr<KeyEntry> synthesizeKeyRepeatLocked(nsecs_t currentTime) REQUIRES(mLock);
 
-    // Key replacement tracking
-    struct KeyReplacement {
-        int32_t keyCode;
-        int32_t deviceId;
-        bool operator==(const KeyReplacement& rhs) const {
-            return keyCode == rhs.keyCode && deviceId == rhs.deviceId;
-        }
-    };
-    struct KeyReplacementHash {
-        size_t operator()(const KeyReplacement& key) const {
-            return std::hash<int32_t>()(key.keyCode) ^ (std::hash<int32_t>()(key.deviceId) << 1);
-        }
-    };
-    // Maps the key code replaced, device id tuple to the key code it was replaced with
-    std::unordered_map<KeyReplacement, int32_t, KeyReplacementHash> mReplacedKeys GUARDED_BY(mLock);
-    // Process certain Meta + Key combinations
-    void accelerateMetaShortcuts(const int32_t deviceId, const int32_t action, int32_t& keyCode,
-                                 int32_t& metaState);
-
     // Deferred command processing.
     bool haveCommandsLocked() const REQUIRES(mLock);
     bool runCommandsLockedInterruptable() REQUIRES(mLock);
@@ -374,14 +354,11 @@
     // Get a reference to window handles by display, return an empty vector if not found.
     const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesLocked(
             int32_t displayId) const REQUIRES(mLock);
-    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
-            const sp<IBinder>& windowHandleToken) const REQUIRES(mLock);
     ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);
 
-    // Same function as above, but faster. Since displayId is provided, this avoids the need
-    // to loop through all displays.
-    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(const sp<IBinder>& windowHandleToken,
-                                                             int displayId) const REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
+            const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId = {}) const
+            REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
     std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const
@@ -542,13 +519,12 @@
     // shade is pulled down while we are counting down the timeout).
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
-    bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) const;
     int32_t getTargetDisplayId(const EventEntry& entry);
     sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
             nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<InputTarget> findTouchedWindowTargetsLocked(
-            nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+            nsecs_t currentTime, const MotionEntry& entry,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
@@ -559,9 +535,13 @@
             std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                ftl::Flags<InputTarget::Flags> targetFlags,
-                               std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                std::optional<nsecs_t> firstDownTimeInTarget,
                                std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
+    void addPointerWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                      ftl::Flags<InputTarget::Flags> targetFlags,
+                                      std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                      std::optional<nsecs_t> firstDownTimeInTarget,
+                                      std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
     void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
             REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
@@ -619,8 +599,8 @@
     void abortBrokenDispatchCycleLocked(nsecs_t currentTime,
                                         const std::shared_ptr<Connection>& connection, bool notify)
             REQUIRES(mLock);
-    void drainDispatchQueue(std::deque<DispatchEntry*>& queue);
-    void releaseDispatchEntry(DispatchEntry* dispatchEntry);
+    void drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue);
+    void releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry);
     int handleReceiveCallback(int events, sp<IBinder> connectionToken);
     // The action sent should only be of type AMOTION_EVENT_*
     void dispatchPointerDownOutsideFocus(uint32_t source, int32_t action,
@@ -685,10 +665,10 @@
             REQUIRES(mLock);
     std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
     bool afterKeyEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
-                                          DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
+                                          DispatchEntry& dispatchEntry, KeyEntry& keyEntry,
                                           bool handled) REQUIRES(mLock);
     bool afterMotionEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
-                                             DispatchEntry* dispatchEntry, MotionEntry& motionEntry,
+                                             DispatchEntry& dispatchEntry, MotionEntry& motionEntry,
                                              bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.cpp b/services/inputflinger/dispatcher/InputEventTimeline.cpp
index 3edb638..a7c6d16 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.cpp
+++ b/services/inputflinger/dispatcher/InputEventTimeline.cpp
@@ -16,6 +16,8 @@
 
 #include "InputEventTimeline.h"
 
+#include "../InputDeviceMetricsSource.h"
+
 namespace android::inputdispatcher {
 
 ConnectionTimeline::ConnectionTimeline(nsecs_t deliveryTime, nsecs_t consumeTime,
@@ -64,8 +66,15 @@
     return !operator==(rhs);
 }
 
-InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime)
-      : isDown(isDown), eventTime(eventTime), readTime(readTime) {}
+InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime,
+                                       uint16_t vendorId, uint16_t productId,
+                                       std::set<InputDeviceUsageSource> sources)
+      : isDown(isDown),
+        eventTime(eventTime),
+        readTime(readTime),
+        vendorId(vendorId),
+        productId(productId),
+        sources(sources) {}
 
 bool InputEventTimeline::operator==(const InputEventTimeline& rhs) const {
     if (connectionTimelines.size() != rhs.connectionTimelines.size()) {
@@ -80,7 +89,8 @@
             return false;
         }
     }
-    return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime;
+    return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime &&
+            vendorId == rhs.vendorId && productId == rhs.productId && sources == rhs.sources;
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h
index daf375d..e9deb2d 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.h
+++ b/services/inputflinger/dispatcher/InputEventTimeline.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "../InputDeviceMetricsSource.h"
+
 #include <binder/IBinder.h>
 #include <input/Input.h>
 #include <unordered_map>
@@ -73,10 +75,14 @@
 };
 
 struct InputEventTimeline {
-    InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime);
+    InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId,
+                       uint16_t productId, std::set<InputDeviceUsageSource> sources);
     const bool isDown; // True if this is an ACTION_DOWN event
     const nsecs_t eventTime;
     const nsecs_t readTime;
+    const uint16_t vendorId;
+    const uint16_t productId;
+    const std::set<InputDeviceUsageSource> sources;
 
     struct IBinderHash {
         std::size_t operator()(const sp<IBinder>& b) const {
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 2fcb89a..09b5186 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,11 +24,24 @@
 
 namespace android::inputdispatcher {
 
+namespace {
+bool isHoverAction(int32_t action) {
+    switch (MotionEvent::getActionMasked(action)) {
+        case AMOTION_EVENT_ACTION_HOVER_ENTER:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+            return true;
+        }
+    }
+    return false;
+}
+} // namespace
+
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
 
-bool InputState::isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const {
+bool InputState::isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const {
     for (const MotionMemento& memento : mMotionMementos) {
         if (memento.deviceId == deviceId && memento.source == source &&
             memento.displayId == displayId && memento.hovering) {
@@ -83,7 +96,34 @@
     }
 }
 
+/**
+ * Return:
+ *  true if the incoming event was correctly tracked,
+ *  false if the incoming event should be dropped.
+ */
 bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
+    // Don't track non-pointer events
+    if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        // This is a focus-dispatched event; we don't track its state.
+        return true;
+    }
+
+    if (!mMotionMementos.empty()) {
+        const MotionMemento& lastMemento = mMotionMementos.back();
+        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+            !isStylusEvent(entry.source, entry.pointerProperties)) {
+            // We already have a stylus stream, and the new event is not from stylus.
+            if (!lastMemento.hovering) {
+                // If stylus is currently down, reject the new event unconditionally.
+                return false;
+            }
+        }
+        if (!lastMemento.hovering && isHoverAction(action)) {
+            // Reject hovers if already down
+            return false;
+        }
+    }
+
     int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
     switch (actionMasked) {
         case AMOTION_EVENT_ACTION_UP:
@@ -230,8 +270,10 @@
 }
 
 void InputState::MotionMemento::setPointers(const MotionEntry& entry) {
-    pointerCount = 0;
-    for (uint32_t i = 0; i < entry.pointerCount; i++) {
+    pointerProperties.clear();
+    pointerCoords.clear();
+
+    for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
         if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) {
             // In POINTER_UP events, the pointer is leaving. Since the action is not stored,
             // this departing pointer should not be recorded.
@@ -240,23 +282,155 @@
                 continue;
             }
         }
-        pointerProperties[pointerCount].copyFrom(entry.pointerProperties[i]);
-        pointerCoords[pointerCount].copyFrom(entry.pointerCoords[i]);
-        pointerCount++;
+        pointerProperties.push_back(entry.pointerProperties[i]);
+        pointerCoords.push_back(entry.pointerCoords[i]);
     }
 }
 
 void InputState::MotionMemento::mergePointerStateTo(MotionMemento& other) const {
-    for (uint32_t i = 0; i < pointerCount; i++) {
+    for (uint32_t i = 0; i < getPointerCount(); i++) {
         if (other.firstNewPointerIdx < 0) {
-            other.firstNewPointerIdx = other.pointerCount;
+            other.firstNewPointerIdx = other.getPointerCount();
         }
-        other.pointerProperties[other.pointerCount].copyFrom(pointerProperties[i]);
-        other.pointerCoords[other.pointerCount].copyFrom(pointerCoords[i]);
-        other.pointerCount++;
+        other.pointerProperties.push_back(pointerProperties[i]);
+        other.pointerCoords.push_back(pointerCoords[i]);
     }
 }
 
+size_t InputState::MotionMemento::getPointerCount() const {
+    return pointerProperties.size();
+}
+
+bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry,
+                                            int32_t resolvedAction) const {
+    if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        // This is a focus-dispatched event that should not affect the previous stream.
+        return false;
+    }
+
+    // New MotionEntry pointer event is coming in.
+
+    // If this is a new gesture, and it's from a different device, then, in general, we will cancel
+    // the current gesture.
+    // However, because stylus should be preferred over touch, we need to treat some cases in a
+    // special way.
+    if (mMotionMementos.empty()) {
+        // There is no ongoing pointer gesture, so there is nothing to cancel
+        return false;
+    }
+
+    const MotionMemento& lastMemento = mMotionMementos.back();
+    const int32_t actionMasked = MotionEvent::getActionMasked(resolvedAction);
+
+    // For compatibility, only one input device can be active at a time in the same window.
+    if (lastMemento.deviceId == motionEntry.deviceId) {
+        // In general, the same device should produce self-consistent streams so nothing needs to
+        // be canceled. But there is one exception:
+        // Sometimes ACTION_DOWN is received without a corresponding HOVER_EXIT. To account for
+        // that, cancel the previous hovering stream
+        if (actionMasked == AMOTION_EVENT_ACTION_DOWN && lastMemento.hovering) {
+            return true;
+        }
+
+        // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
+        // If this hover event was generated as a result of the pointer leaving the window,
+        // the HOVER_EXIT event should have the same coordinates as the previous
+        // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
+        // coordinates as the previous event by cancelling the stream here. With this approach, the
+        // HOVER_EXIT event is generated from the previous event.
+        if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
+            return true;
+        }
+
+        // If the stream changes its source, just cancel the current gesture to be safe. It's
+        // possible that the app isn't handling source changes properly
+        if (motionEntry.source != lastMemento.source) {
+            LOG(INFO) << "Canceling stream: last source was "
+                      << inputEventSourceToString(lastMemento.source) << " and new event is "
+                      << motionEntry;
+            return true;
+        }
+
+        // If the injection is happening into two different displays, the same injected device id
+        // could be going into both. And at this time, if mirroring is active, the same connection
+        // would receive different events from each display. Since the TouchStates are per-display,
+        // it's unlikely that those two streams would be consistent with each other. Therefore,
+        // cancel the previous gesture if the display id changes.
+        if (motionEntry.displayId != lastMemento.displayId) {
+            LOG(INFO) << "Canceling stream: last displayId was "
+                      << inputEventSourceToString(lastMemento.displayId) << " and new event is "
+                      << motionEntry;
+            return true;
+        }
+
+        return false;
+    }
+
+    // We want stylus down to block touch and other source types, but stylus hover should not
+    // have such an effect.
+    if (isHoverAction(motionEntry.action) && !lastMemento.hovering) {
+        // New event is a hover. Keep the current non-hovering gesture instead
+        return false;
+    }
+
+    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) {
+        // We have non-hovering stylus already active.
+        if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+            actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+            // If this new event is a stylus from a different device going down, then cancel the old
+            // stylus and allow the new stylus to take over
+            return true;
+        }
+
+        // Keep the current stylus gesture.
+        return false;
+    }
+
+    // Cancel the current gesture if this is a start of a new gesture from a new device.
+    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+        actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+        return true;
+    }
+    // By default, don't cancel any events.
+    return false;
+}
+
+std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(const MotionEntry& motionEntry,
+                                                                     int32_t resolvedAction) {
+    if (!shouldCancelPreviousStream(motionEntry, resolvedAction)) {
+        return {};
+    }
+
+    const MotionMemento& memento = mMotionMementos.back();
+
+    // Cancel the last device stream
+    std::unique_ptr<MotionEntry> cancelEntry =
+            createCancelEntryForMemento(memento, motionEntry.eventTime);
+
+    if (!trackMotion(*cancelEntry, cancelEntry->action, cancelEntry->flags)) {
+        LOG(FATAL) << "Generated inconsistent cancel event!";
+    }
+    return cancelEntry;
+}
+
+std::unique_ptr<MotionEntry> InputState::createCancelEntryForMemento(const MotionMemento& memento,
+                                                                     nsecs_t eventTime) const {
+    const int32_t action =
+            memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+    int32_t flags = memento.flags;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    return std::make_unique<MotionEntry>(mIdGenerator.nextId(), eventTime, memento.deviceId,
+                                         memento.source, memento.displayId, memento.policyFlags,
+                                         action, /*actionButton=*/0, flags, AMETA_NONE,
+                                         /*buttonState=*/0, MotionClassification::NONE,
+                                         AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+                                         memento.yPrecision, memento.xCursorPosition,
+                                         memento.yCursorPosition, memento.downTime,
+                                         memento.pointerProperties, memento.pointerCoords);
+}
+
 std::vector<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents(
         nsecs_t currentTime, const CancelationOptions& options) {
     std::vector<std::unique_ptr<EventEntry>> events;
@@ -275,25 +449,7 @@
     for (const MotionMemento& memento : mMotionMementos) {
         if (shouldCancelMotion(memento, options)) {
             if (options.pointerIds == std::nullopt) {
-                const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
-                                                        : AMOTION_EVENT_ACTION_CANCEL;
-                int32_t flags = memento.flags;
-                if (action == AMOTION_EVENT_ACTION_CANCEL) {
-                    flags |= AMOTION_EVENT_FLAG_CANCELED;
-                }
-                events.push_back(
-                        std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
-                                                      memento.deviceId, memento.source,
-                                                      memento.displayId, memento.policyFlags,
-                                                      action, /*actionButton=*/0, flags, AMETA_NONE,
-                                                      /*buttonState=*/0, MotionClassification::NONE,
-                                                      AMOTION_EVENT_EDGE_FLAG_NONE,
-                                                      memento.xPrecision, memento.yPrecision,
-                                                      memento.xCursorPosition,
-                                                      memento.yCursorPosition, memento.downTime,
-                                                      memento.pointerCount,
-                                                      memento.pointerProperties,
-                                                      memento.pointerCoords));
+                events.push_back(createCancelEntryForMemento(memento, currentTime));
             } else {
                 std::vector<std::unique_ptr<MotionEntry>> pointerCancelEvents =
                         synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(),
@@ -310,7 +466,7 @@
         nsecs_t currentTime) {
     std::vector<std::unique_ptr<EventEntry>> events;
     for (MotionMemento& memento : mMotionMementos) {
-        if (!(memento.source & AINPUT_SOURCE_CLASS_POINTER)) {
+        if (!isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
             continue;
         }
 
@@ -318,24 +474,22 @@
             continue;
         }
 
-        uint32_t pointerCount = 0;
-        PointerProperties pointerProperties[MAX_POINTERS];
-        PointerCoords pointerCoords[MAX_POINTERS];
+        std::vector<PointerProperties> pointerProperties;
+        std::vector<PointerCoords> pointerCoords;
 
         // We will deliver all pointers the target already knows about
         for (uint32_t i = 0; i < static_cast<uint32_t>(memento.firstNewPointerIdx); i++) {
-            pointerProperties[i].copyFrom(memento.pointerProperties[i]);
-            pointerCoords[i].copyFrom(memento.pointerCoords[i]);
-            pointerCount++;
+            pointerProperties.push_back(memento.pointerProperties[i]);
+            pointerCoords.push_back(memento.pointerCoords[i]);
         }
 
         // We will send explicit events for all pointers the target doesn't know about
         for (uint32_t i = static_cast<uint32_t>(memento.firstNewPointerIdx);
-                i < memento.pointerCount; i++) {
+             i < memento.getPointerCount(); i++) {
+            pointerProperties.push_back(memento.pointerProperties[i]);
+            pointerCoords.push_back(memento.pointerCoords[i]);
 
-            pointerProperties[i].copyFrom(memento.pointerProperties[i]);
-            pointerCoords[i].copyFrom(memento.pointerCoords[i]);
-            pointerCount++;
+            const size_t pointerCount = pointerProperties.size();
 
             // Down only if the first pointer, pointer down otherwise
             const int32_t action = (pointerCount <= 1)
@@ -352,7 +506,7 @@
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
-                                                  pointerCount, pointerProperties, pointerCoords));
+                                                  pointerProperties, pointerCoords));
         }
 
         memento.firstNewPointerIdx = INVALID_POINTER_INDEX;
@@ -368,16 +522,16 @@
     std::vector<uint32_t> canceledPointerIndices;
     std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
     std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
-    for (uint32_t pointerIdx = 0; pointerIdx < memento.pointerCount; pointerIdx++) {
+    for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) {
         uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
-        pointerProperties[pointerIdx].copyFrom(memento.pointerProperties[pointerIdx]);
-        pointerCoords[pointerIdx].copyFrom(memento.pointerCoords[pointerIdx]);
+        pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx];
+        pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx];
         if (pointerIds.test(pointerId)) {
             canceledPointerIndices.push_back(pointerIdx);
         }
     }
 
-    if (canceledPointerIndices.size() == memento.pointerCount) {
+    if (canceledPointerIndices.size() == memento.getPointerCount()) {
         const int32_t action =
                 memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
         int32_t flags = memento.flags;
@@ -393,8 +547,7 @@
                                               AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                               memento.yPrecision, memento.xCursorPosition,
                                               memento.yCursorPosition, memento.downTime,
-                                              memento.pointerCount, memento.pointerProperties,
-                                              memento.pointerCoords));
+                                              memento.pointerProperties, memento.pointerCoords));
     } else {
         // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
         // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
@@ -405,7 +558,7 @@
         std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
                   std::greater<uint32_t>());
 
-        uint32_t pointerCount = memento.pointerCount;
+        uint32_t pointerCount = memento.getPointerCount();
         for (const uint32_t pointerIdx : canceledPointerIndices) {
             const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
                                                      : AMOTION_EVENT_ACTION_POINTER_UP |
@@ -421,8 +574,7 @@
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
-                                                  pointerCount, pointerProperties.data(),
-                                                  pointerCoords.data()));
+                                                  pointerProperties, pointerCoords));
 
             // Cleanup pointer information
             pointerProperties.erase(pointerProperties.begin() + pointerIdx);
@@ -444,7 +596,7 @@
         MotionMemento& memento = mMotionMementos[i];
         // Since we support split pointers we need to merge touch events
         // from the same source + device + screen.
-        if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+        if (isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
             bool merged = false;
             for (size_t j = 0; j < other.mMotionMementos.size(); j++) {
                 MotionMemento& otherMemento = other.mMotionMementos[j];
@@ -526,4 +678,14 @@
     }
 }
 
+std::ostream& operator<<(std::ostream& out, const InputState& state) {
+    if (!state.mMotionMementos.empty()) {
+        out << "mMotionMementos: ";
+        for (const InputState::MotionMemento& memento : state.mMotionMementos) {
+            out << "{deviceId= " << memento.deviceId << ", hovering=" << memento.hovering << "}, ";
+        }
+    }
+    return out;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index af2a3cb..686c432 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -36,7 +36,7 @@
 
     // Returns true if the specified source is known to have received a hover enter
     // motion event.
-    bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const;
+    bool isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const;
 
     // Records tracking information for a key event that has just been published.
     // Returns true if the event should be delivered, false if it is inconsistent
@@ -48,6 +48,10 @@
     // and should be skipped.
     bool trackMotion(const MotionEntry& entry, int32_t action, int32_t flags);
 
+    // Create cancel events for the previous stream if the current motionEntry requires it.
+    std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry,
+                                                             int32_t resolvedAction);
+
     // Synthesizes cancelation events for the current state and resets the tracked state.
     std::vector<std::unique_ptr<EventEntry>> synthesizeCancelationEvents(
             nsecs_t currentTime, const CancelationOptions& options);
@@ -76,7 +80,7 @@
 
 private:
     struct KeyMemento {
-        int32_t deviceId;
+        DeviceId deviceId;
         uint32_t source;
         int32_t displayId;
         int32_t keyCode;
@@ -88,7 +92,7 @@
     };
 
     struct MotionMemento {
-        int32_t deviceId;
+        DeviceId deviceId;
         uint32_t source;
         int32_t displayId;
         int32_t flags;
@@ -97,9 +101,8 @@
         float xCursorPosition;
         float yCursorPosition;
         nsecs_t downTime;
-        uint32_t pointerCount;
-        PointerProperties pointerProperties[MAX_POINTERS];
-        PointerCoords pointerCoords[MAX_POINTERS];
+        std::vector<PointerProperties> pointerProperties;
+        std::vector<PointerCoords> pointerCoords;
         // Track for which pointers the target doesn't know about.
         int32_t firstNewPointerIdx = INVALID_POINTER_INDEX;
         bool hovering;
@@ -107,6 +110,7 @@
 
         void setPointers(const MotionEntry& entry);
         void mergePointerStateTo(MotionMemento& other) const;
+        size_t getPointerCount() const;
     };
 
     const IdGenerator& mIdGenerator; // InputDispatcher owns it so we won't have dangling reference.
@@ -123,12 +127,18 @@
 
     static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options);
     static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options);
+    bool shouldCancelPreviousStream(const MotionEntry& motionEntry, int32_t resolvedAction) const;
+    std::unique_ptr<MotionEntry> createCancelEntryForMemento(const MotionMemento& memento,
+                                                             nsecs_t eventTime) const;
 
     // Synthesizes pointer cancel events for a particular set of pointers.
     std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
             const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
             nsecs_t currentTime);
+    friend std::ostream& operator<<(std::ostream& out, const InputState& state);
 };
 
+std::ostream& operator<<(std::ostream& out, const InputState& state);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index fc8b785..343630c 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -16,7 +16,9 @@
 
 #include "InputTarget.h"
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
 #include <inttypes.h>
 #include <string>
 
@@ -34,7 +36,10 @@
     }
 
     // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
-    LOG_ALWAYS_FATAL_IF((pointerIds & newPointerIds).any());
+    if ((pointerIds & newPointerIds).any()) {
+        LOG(FATAL) << __func__ << " - overlap with incoming pointers "
+                   << bitsetToString(newPointerIds) << " in " << *this;
+    }
 
     pointerIds |= newPointerIds;
     for (size_t i = 0; i < newPointerIds.size(); i++) {
@@ -76,4 +81,24 @@
     }
     return out;
 }
+
+std::ostream& operator<<(std::ostream& out, const InputTarget& target) {
+    out << "{inputChannel=";
+    if (target.inputChannel != nullptr) {
+        out << target.inputChannel->getName();
+    } else {
+        out << "<null>";
+    }
+    out << ", windowHandle=";
+    if (target.windowHandle != nullptr) {
+        out << target.windowHandle->getName();
+    } else {
+        out << "<null>";
+    }
+    out << ", targetFlags=" << target.flags.string();
+    out << ", pointers=" << target.getPointerInfoString();
+    out << "}";
+    return out;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 3bf8b68..8b8a35a 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -139,6 +139,6 @@
     std::string getPointerInfoString() const;
 };
 
-std::string dispatchModeToString(int32_t dispatchMode);
+std::ostream& operator<<(std::ostream& out, const InputTarget& target);
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
index 96d78c3..e09d97a 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -126,6 +126,7 @@
 }
 
 void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
+    std::scoped_lock lock(mLock);
     // Before we do any processing, check that we have not yet exceeded MAX_SIZE
     if (mNumSketchEventsProcessed >= MAX_EVENTS_FOR_STATISTICS) {
         return;
@@ -167,6 +168,7 @@
 }
 
 AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullData(AStatsEventList* data) {
+    std::scoped_lock lock(mLock);
     std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedDownData;
     std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedMoveData;
     for (size_t i = 0; i < SketchIndex::SIZE; i++) {
@@ -257,6 +259,7 @@
 }
 
 std::string LatencyAggregator::dump(const char* prefix) const {
+    std::scoped_lock lock(mLock);
     std::string sketchDump = StringPrintf("%s  Sketches:\n", prefix);
     for (size_t i = 0; i < SketchIndex::SIZE; i++) {
         const int64_t numDown = mDownSketches[i]->num_values();
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
index 60b6813..d6d1686 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.h
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/thread_annotations.h>
 #include <kll.h>
 #include <statslog.h>
 #include <utils/Timers.h>
@@ -61,10 +62,13 @@
     ~LatencyAggregator();
 
 private:
+    // Binder call -- called on a binder thread. This is different from the thread where the rest of
+    // the public API is called.
     static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atom_tag,
                                                                  AStatsEventList* data,
                                                                  void* cookie);
     AStatsManager_PullAtomCallbackReturn pullData(AStatsEventList* data);
+
     // ---------- Slow event handling ----------
     void processSlowEvent(const InputEventTimeline& timeline);
     nsecs_t mLastSlowEventTime = 0;
@@ -74,14 +78,17 @@
     size_t mNumEventsSinceLastSlowEventReport = 0;
 
     // ---------- Statistics handling ----------
+    // Statistics is pulled rather than pushed. It's pulled on a binder thread, and therefore will
+    // be accessed by two different threads. The lock is needed to protect the pulled data.
+    mutable std::mutex mLock;
     void processStatistics(const InputEventTimeline& timeline);
     // Sketches
     std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
-            mDownSketches;
+            mDownSketches GUARDED_BY(mLock);
     std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
-            mMoveSketches;
+            mMoveSketches GUARDED_BY(mLock);
     // How many events have been processed so far
-    size_t mNumSketchEventsProcessed = 0;
+    size_t mNumSketchEventsProcessed GUARDED_BY(mLock) = 0;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index b7c36a8..698bd9f 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "LatencyTracker"
 #include "LatencyTracker.h"
+#include "../InputDeviceMetricsSource.h"
 
 #include <inttypes.h>
 
@@ -23,6 +24,7 @@
 #include <android-base/stringprintf.h>
 #include <android/os/IInputConstants.h>
 #include <input/Input.h>
+#include <input/InputDevice.h>
 #include <log/log.h>
 
 using android::base::HwTimeoutMultiplier;
@@ -66,7 +68,8 @@
 }
 
 void LatencyTracker::trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime,
-                                   nsecs_t readTime) {
+                                   nsecs_t readTime, DeviceId deviceId,
+                                   const std::set<InputDeviceUsageSource>& sources) {
     reportAndPruneMatureRecords(eventTime);
     const auto it = mTimelines.find(inputEventId);
     if (it != mTimelines.end()) {
@@ -78,7 +81,29 @@
         eraseByValue(mEventTimes, inputEventId);
         return;
     }
-    mTimelines.emplace(inputEventId, InputEventTimeline(isDown, eventTime, readTime));
+
+    // Create an InputEventTimeline for the device ID. The vendorId and productId
+    // can be obtained from the InputDeviceIdentifier of the particular device.
+    const InputDeviceIdentifier* identifier = nullptr;
+    for (auto& inputDevice : mInputDevices) {
+        if (deviceId == inputDevice.getId()) {
+            identifier = &inputDevice.getIdentifier();
+            break;
+        }
+    }
+
+    // If no matching ids can be found for the device from among the input devices connected,
+    // the call to trackListener will be dropped.
+    // Note: there generally isn't expected to be a situation where we can't find an InputDeviceInfo
+    // but a possibility of it is handled in case of race conditions
+    if (identifier == nullptr) {
+        ALOGE("Could not find input device identifier. Dropping call to LatencyTracker.");
+        return;
+    }
+
+    mTimelines.emplace(inputEventId,
+                       InputEventTimeline(isDown, eventTime, readTime, identifier->vendor,
+                                          identifier->product, sources));
     mEventTimes.emplace(eventTime, inputEventId);
 }
 
@@ -171,4 +196,8 @@
             StringPrintf("%s  mEventTimes.size() = %zu\n", prefix, mEventTimes.size());
 }
 
+void LatencyTracker::setInputDevices(const std::vector<InputDeviceInfo>& inputDevices) {
+    mInputDevices = inputDevices;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
index 4212da8..890d61d 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.h
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "../InputDeviceMetricsSource.h"
+
 #include <map>
 #include <unordered_map>
 
@@ -23,6 +25,7 @@
 #include <input/Input.h>
 
 #include "InputEventTimeline.h"
+#include "NotifyArgs.h"
 
 namespace android::inputdispatcher {
 
@@ -49,13 +52,15 @@
      * duplicate events that happen to have the same eventTime and inputEventId. Therefore, we
      * must drop all duplicate data.
      */
-    void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime);
+    void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime,
+                       DeviceId deviceId, const std::set<InputDeviceUsageSource>& sources);
     void trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
                             nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
     void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
                               std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
 
     std::string dump(const char* prefix) const;
+    void setInputDevices(const std::vector<InputDeviceInfo>& inputDevices);
 
 private:
     /**
@@ -76,6 +81,7 @@
     std::multimap<nsecs_t /*eventTime*/, int32_t /*inputEventId*/> mEventTimes;
 
     InputEventTimelineProcessor* mTimelineProcessor;
+    std::vector<InputDeviceInfo> mInputDevices;
     void reportAndPruneMatureRecords(nsecs_t newEventTime);
 };
 
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index dadfdc1..2ead171 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <gui/WindowInfo.h>
 
@@ -31,43 +32,34 @@
     *this = TouchState();
 }
 
-std::set<int32_t> TouchState::getActiveDeviceIds() const {
-    std::set<int32_t> out;
-    for (const TouchedWindow& w : windows) {
-        std::set<int32_t> deviceIds = w.getActiveDeviceIds();
-        out.insert(deviceIds.begin(), deviceIds.end());
-    }
-    return out;
-}
-
-bool TouchState::hasTouchingPointers(int32_t deviceId) const {
+bool TouchState::hasTouchingPointers(DeviceId deviceId) const {
     return std::any_of(windows.begin(), windows.end(), [&](const TouchedWindow& window) {
         return window.hasTouchingPointers(deviceId);
     });
 }
 
-void TouchState::removeTouchingPointer(int32_t removedDeviceId, int32_t pointerId) {
+void TouchState::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
     for (TouchedWindow& touchedWindow : windows) {
-        touchedWindow.removeTouchingPointer(removedDeviceId, pointerId);
+        touchedWindow.removeTouchingPointer(deviceId, pointerId);
     }
     clearWindowsWithoutPointers();
 }
 
 void TouchState::removeTouchingPointerFromWindow(
-        int32_t removedDeviceId, int32_t pointerId,
+        DeviceId deviceId, int32_t pointerId,
         const sp<android::gui::WindowInfoHandle>& windowHandle) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.removeTouchingPointer(removedDeviceId, pointerId);
+            touchedWindow.removeTouchingPointer(deviceId, pointerId);
             clearWindowsWithoutPointers();
             return;
         }
     }
 }
 
-void TouchState::clearHoveringPointers() {
+void TouchState::clearHoveringPointers(DeviceId deviceId) {
     for (TouchedWindow& touchedWindow : windows) {
-        touchedWindow.clearHoveringPointers();
+        touchedWindow.removeAllHoveringPointersForDevice(deviceId);
     }
     clearWindowsWithoutPointers();
 }
@@ -79,13 +71,19 @@
 }
 
 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
-                                   ftl::Flags<InputTarget::Flags> targetFlags,
-                                   int32_t addedDeviceId,
+                                   ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
                                    std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
                                    std::optional<nsecs_t> firstDownTimeInTarget) {
+    if (touchingPointerIds.none()) {
+        LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
+        return;
+    }
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
-        // may have a different transform
+        // may have a different transform. They will be combined later when we create InputTargets.
+        // At that point, per-pointer window transform will be considered.
+        // An alternative design choice here would have been to compare here by token, but to
+        // store per-pointer transform.
         if (touchedWindow.windowHandle == windowHandle) {
             touchedWindow.targetFlags |= targetFlags;
             if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
@@ -94,9 +92,9 @@
             // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
             // downTime set initially. Need to update existing window when a pointer is down for the
             // window.
-            touchedWindow.addTouchingPointers(addedDeviceId, touchingPointerIds);
+            touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
             if (firstDownTimeInTarget) {
-                touchedWindow.trySetDownTimeInTarget(addedDeviceId, *firstDownTimeInTarget);
+                touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
             }
             return;
         }
@@ -104,25 +102,25 @@
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
     touchedWindow.targetFlags = targetFlags;
-    touchedWindow.addTouchingPointers(addedDeviceId, touchingPointerIds);
+    touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
     if (firstDownTimeInTarget) {
-        touchedWindow.trySetDownTimeInTarget(addedDeviceId, *firstDownTimeInTarget);
+        touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
     }
     windows.push_back(touchedWindow);
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+                                            DeviceId deviceId, int32_t hoveringPointerId) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+            touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+    touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
     windows.push_back(touchedWindow);
 }
 
@@ -149,12 +147,12 @@
     }
 }
 
-void TouchState::cancelPointersForWindowsExcept(int32_t touchedDeviceId,
+void TouchState::cancelPointersForWindowsExcept(DeviceId deviceId,
                                                 std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                                 const sp<IBinder>& token) {
     std::for_each(windows.begin(), windows.end(), [&](TouchedWindow& w) {
         if (w.windowHandle->getToken() != token) {
-            w.removeTouchingPointers(touchedDeviceId, pointerIds);
+            w.removeTouchingPointers(deviceId, pointerIds);
         }
     });
     clearWindowsWithoutPointers();
@@ -168,10 +166,10 @@
  */
 void TouchState::cancelPointersForNonPilferingWindows() {
     // First, find all pointers that are being pilfered, across all windows
-    std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> allPilferedPointerIdsByDevice;
+    std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> allPilferedPointerIdsByDevice;
     for (const TouchedWindow& w : windows) {
-        for (const auto& [iterDeviceId, pilferedPointerIds] : w.getPilferingPointers()) {
-            allPilferedPointerIdsByDevice[iterDeviceId] |= pilferedPointerIds;
+        for (const auto& [deviceId, pilferedPointerIds] : w.getPilferingPointers()) {
+            allPilferedPointerIdsByDevice[deviceId] |= pilferedPointerIds;
         }
     };
 
@@ -183,12 +181,12 @@
     // (only), the remove pointer 2 from window A and pointer 1 from window B. Usually, the set of
     // pilfered pointers will be disjoint across all windows, but there's no reason to cause that
     // limitation here.
-    for (const auto& [iterDeviceId, allPilferedPointerIds] : allPilferedPointerIdsByDevice) {
+    for (const auto& [deviceId, allPilferedPointerIds] : allPilferedPointerIdsByDevice) {
         std::for_each(windows.begin(), windows.end(), [&](TouchedWindow& w) {
             std::bitset<MAX_POINTER_ID + 1> pilferedByOtherWindows =
-                    w.getPilferingPointers(iterDeviceId) ^ allPilferedPointerIds;
+                    w.getPilferingPointers(deviceId) ^ allPilferedPointerIds;
             // Remove all pointers pilfered by other windows
-            w.removeTouchingPointers(iterDeviceId, pilferedByOtherWindows);
+            w.removeTouchingPointers(deviceId, pilferedByOtherWindows);
         });
     }
     clearWindowsWithoutPointers();
@@ -238,21 +236,23 @@
     return *it;
 }
 
-bool TouchState::isDown() const {
-    return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return window.hasTouchingPointers(); });
+bool TouchState::isDown(DeviceId deviceId) const {
+    return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+        return window.hasTouchingPointers(deviceId);
+    });
 }
 
-bool TouchState::hasHoveringPointers() const {
-    return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return window.hasHoveringPointers(); });
+bool TouchState::hasHoveringPointers(DeviceId deviceId) const {
+    return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+        return window.hasHoveringPointers(deviceId);
+    });
 }
 
-std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
+std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
                                                                          int32_t pointerId) const {
     std::set<sp<WindowInfoHandle>> out;
     for (const TouchedWindow& window : windows) {
-        if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) {
+        if (window.hasHoveringPointer(deviceId, pointerId)) {
             out.insert(window.windowHandle);
         }
     }
@@ -266,10 +266,10 @@
     clearWindowsWithoutPointers();
 }
 
-void TouchState::removeAllPointersForDevice(int32_t removedDeviceId) {
+void TouchState::removeAllPointersForDevice(DeviceId deviceId) {
     for (TouchedWindow& window : windows) {
-        window.removeAllHoveringPointersForDevice(removedDeviceId);
-        window.removeAllTouchingPointersForDevice(removedDeviceId);
+        window.removeAllHoveringPointersForDevice(deviceId);
+        window.removeAllTouchingPointersForDevice(deviceId);
     }
 
     clearWindowsWithoutPointers();
@@ -289,4 +289,9 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchState& state) {
+    out << state.dump();
+    return out;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9f29a4a..e79c73b 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <bitset>
+#include <ostream>
 #include <set>
 #include "TouchedWindow.h"
 
@@ -38,27 +39,25 @@
     void reset();
     void clearWindowsWithoutPointers();
 
-    std::set<int32_t> getActiveDeviceIds() const;
-
-    bool hasTouchingPointers(int32_t device) const;
-    void removeTouchingPointer(int32_t deviceId, int32_t pointerId);
-    void removeTouchingPointerFromWindow(int32_t deviceId, int32_t pointerId,
+    bool hasTouchingPointers(DeviceId deviceId) const;
+    void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
+    void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
                                          const sp<android::gui::WindowInfoHandle>& windowHandle);
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                           ftl::Flags<InputTarget::Flags> targetFlags, int32_t deviceId,
+                           ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
                            std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
                            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    int32_t deviceId, int32_t hoveringPointerId);
-    void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
-    void clearHoveringPointers();
+                                    DeviceId deviceId, int32_t hoveringPointerId);
+    void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
+    void clearHoveringPointers(DeviceId deviceId);
 
-    void removeAllPointersForDevice(int32_t deviceId);
+    void removeAllPointersForDevice(DeviceId deviceId);
     void removeWindowByToken(const sp<IBinder>& token);
     void filterNonAsIsTouchWindows();
 
     // Cancel pointers for current set of windows except the window with particular binder token.
-    void cancelPointersForWindowsExcept(int32_t deviceId,
+    void cancelPointersForWindowsExcept(DeviceId deviceId,
                                         std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                         const sp<IBinder>& token);
     // Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow
@@ -71,13 +70,15 @@
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
-    bool isDown() const;
-    bool hasHoveringPointers() const;
+    bool isDown(DeviceId deviceId) const;
+    bool hasHoveringPointers(DeviceId deviceId) const;
 
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
-            int32_t deviceId, int32_t pointerId) const;
+            DeviceId deviceId, int32_t pointerId) const;
     std::string dump() const;
 };
 
+std::ostream& operator<<(std::ostream& out, const TouchState& state);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index ae16520..5367751 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -35,7 +35,7 @@
     return false;
 }
 
-bool TouchedWindow::hasHoveringPointers(int32_t deviceId) const {
+bool TouchedWindow::hasHoveringPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return false;
@@ -45,15 +45,19 @@
     return state.hoveringPointerIds.any();
 }
 
-void TouchedWindow::clearHoveringPointers() {
-    for (auto& [_, state] : mDeviceStates) {
-        state.hoveringPointerIds.reset();
+void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
+    auto stateIt = mDeviceStates.find(deviceId);
+    if (stateIt == mDeviceStates.end()) {
+        return;
     }
-
-    std::erase_if(mDeviceStates, [](const auto& pair) { return !pair.second.hasPointers(); });
+    DeviceState& state = stateIt->second;
+    state.hoveringPointerIds.reset();
+    if (!state.hasPointers()) {
+        mDeviceStates.erase(stateIt);
+    }
 }
 
-bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const {
+bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return false;
@@ -63,15 +67,11 @@
     return state.hoveringPointerIds.test(pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) {
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, int32_t pointerId) {
     mDeviceStates[deviceId].hoveringPointerIds.set(pointerId);
 }
 
-void TouchedWindow::addTouchingPointer(int32_t deviceId, int32_t pointerId) {
-    mDeviceStates[deviceId].touchingPointerIds.set(pointerId);
-}
-
-void TouchedWindow::addTouchingPointers(int32_t deviceId,
+void TouchedWindow::addTouchingPointers(DeviceId deviceId,
                                         std::bitset<MAX_POINTER_ID + 1> pointers) {
     mDeviceStates[deviceId].touchingPointerIds |= pointers;
 }
@@ -85,15 +85,15 @@
     return false;
 }
 
-bool TouchedWindow::hasTouchingPointers(int32_t deviceId) const {
+bool TouchedWindow::hasTouchingPointers(DeviceId deviceId) const {
     return getTouchingPointers(deviceId).any();
 }
 
-bool TouchedWindow::hasTouchingPointer(int32_t deviceId, int32_t pointerId) const {
+bool TouchedWindow::hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const {
     return getTouchingPointers(deviceId).test(pointerId);
 }
 
-std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(int32_t deviceId) const {
+std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return {};
@@ -103,14 +103,14 @@
     return state.touchingPointerIds;
 }
 
-void TouchedWindow::removeTouchingPointer(int32_t deviceId, int32_t pointerId) {
+void TouchedWindow::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
     std::bitset<MAX_POINTER_ID + 1> pointerIds;
     pointerIds.set(pointerId, true);
 
     removeTouchingPointers(deviceId, pointerIds);
 }
 
-void TouchedWindow::removeTouchingPointers(int32_t deviceId,
+void TouchedWindow::removeTouchingPointers(DeviceId deviceId,
                                            std::bitset<MAX_POINTER_ID + 1> pointers) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
@@ -126,23 +126,23 @@
     }
 }
 
-std::set<int32_t> TouchedWindow::getTouchingDeviceIds() const {
-    std::set<int32_t> deviceIds;
+std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
+    std::set<DeviceId> deviceIds;
     for (const auto& [deviceId, _] : mDeviceStates) {
         deviceIds.insert(deviceId);
     }
     return deviceIds;
 }
 
-std::set<int32_t> TouchedWindow::getActiveDeviceIds() const {
-    std::set<int32_t> out;
+std::set<DeviceId> TouchedWindow::getActiveDeviceIds() const {
+    std::set<DeviceId> out;
     for (const auto& [deviceId, _] : mDeviceStates) {
         out.emplace(deviceId);
     }
     return out;
 }
 
-bool TouchedWindow::hasPilferingPointers(int32_t deviceId) const {
+bool TouchedWindow::hasPilferingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return false;
@@ -152,16 +152,16 @@
     return state.pilferingPointerIds.any();
 }
 
-void TouchedWindow::addPilferingPointers(int32_t deviceId,
+void TouchedWindow::addPilferingPointers(DeviceId deviceId,
                                          std::bitset<MAX_POINTER_ID + 1> pointerIds) {
     mDeviceStates[deviceId].pilferingPointerIds |= pointerIds;
 }
 
-void TouchedWindow::addPilferingPointer(int32_t deviceId, int32_t pointerId) {
+void TouchedWindow::addPilferingPointer(DeviceId deviceId, int32_t pointerId) {
     mDeviceStates[deviceId].pilferingPointerIds.set(pointerId);
 }
 
-std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getPilferingPointers(int32_t deviceId) const {
+std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getPilferingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return {};
@@ -171,15 +171,15 @@
     return state.pilferingPointerIds;
 }
 
-std::map<int32_t, std::bitset<MAX_POINTER_ID + 1>> TouchedWindow::getPilferingPointers() const {
-    std::map<int32_t, std::bitset<MAX_POINTER_ID + 1>> out;
+std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> TouchedWindow::getPilferingPointers() const {
+    std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> out;
     for (const auto& [deviceId, state] : mDeviceStates) {
         out.emplace(deviceId, state.pilferingPointerIds);
     }
     return out;
 }
 
-std::optional<nsecs_t> TouchedWindow::getDownTimeInTarget(int32_t deviceId) const {
+std::optional<nsecs_t> TouchedWindow::getDownTimeInTarget(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return {};
@@ -188,7 +188,7 @@
     return state.downTimeInTarget;
 }
 
-void TouchedWindow::trySetDownTimeInTarget(int32_t deviceId, nsecs_t downTime) {
+void TouchedWindow::trySetDownTimeInTarget(DeviceId deviceId, nsecs_t downTime) {
     auto [stateIt, _] = mDeviceStates.try_emplace(deviceId);
     DeviceState& state = stateIt->second;
 
@@ -197,7 +197,7 @@
     }
 }
 
-void TouchedWindow::removeAllTouchingPointersForDevice(int32_t deviceId) {
+void TouchedWindow::removeAllTouchingPointersForDevice(DeviceId deviceId) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return;
@@ -213,7 +213,7 @@
     }
 }
 
-void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
+void TouchedWindow::removeHoveringPointer(DeviceId deviceId, int32_t pointerId) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return;
@@ -227,7 +227,7 @@
     }
 }
 
-void TouchedWindow::removeAllHoveringPointersForDevice(int32_t deviceId) {
+void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return;
@@ -260,5 +260,10 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
+    out << window.dump();
+    return out;
+}
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 81393fc..6d2283e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -20,6 +20,7 @@
 #include <input/Input.h>
 #include <utils/BitSet.h>
 #include <bitset>
+#include <ostream>
 #include <set>
 #include "InputTarget.h"
 
@@ -34,44 +35,43 @@
 
     // Hovering
     bool hasHoveringPointers() const;
-    bool hasHoveringPointers(int32_t deviceId) const;
-    bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
-    void addHoveringPointer(int32_t deviceId, int32_t pointerId);
-    void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
+    bool hasHoveringPointers(DeviceId deviceId) const;
+    bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
+    void addHoveringPointer(DeviceId deviceId, int32_t pointerId);
+    void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
-    bool hasTouchingPointer(int32_t deviceId, int32_t pointerId) const;
+    bool hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const;
     bool hasTouchingPointers() const;
-    bool hasTouchingPointers(int32_t deviceId) const;
-    std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(int32_t deviceId) const;
-    void addTouchingPointer(int32_t deviceId, int32_t pointerId);
-    void addTouchingPointers(int32_t deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
-    void removeTouchingPointer(int32_t deviceId, int32_t pointerId);
-    void removeTouchingPointers(int32_t deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    bool hasTouchingPointers(DeviceId deviceId) const;
+    std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(DeviceId deviceId) const;
+    void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
+    void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
     /**
      * Get the currently active touching device id. If there isn't exactly 1 touching device, return
      * nullopt.
      */
-    std::set<int32_t> getTouchingDeviceIds() const;
+    std::set<DeviceId> getTouchingDeviceIds() const;
     /**
      * The ids of devices that are currently touching or hovering.
      */
-    std::set<int32_t> getActiveDeviceIds() const;
+    std::set<DeviceId> getActiveDeviceIds() const;
 
     // Pilfering pointers
-    bool hasPilferingPointers(int32_t deviceId) const;
-    void addPilferingPointers(int32_t deviceId, std::bitset<MAX_POINTER_ID + 1> pointerIds);
-    void addPilferingPointer(int32_t deviceId, int32_t pointerId);
-    std::bitset<MAX_POINTER_ID + 1> getPilferingPointers(int32_t deviceId) const;
-    std::map<int32_t, std::bitset<MAX_POINTER_ID + 1>> getPilferingPointers() const;
+    bool hasPilferingPointers(DeviceId deviceId) const;
+    void addPilferingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointerIds);
+    void addPilferingPointer(DeviceId deviceId, int32_t pointerId);
+    std::bitset<MAX_POINTER_ID + 1> getPilferingPointers(DeviceId deviceId) const;
+    std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> getPilferingPointers() const;
 
     // Down time
-    std::optional<nsecs_t> getDownTimeInTarget(int32_t deviceId) const;
-    void trySetDownTimeInTarget(int32_t deviceId, nsecs_t downTime);
+    std::optional<nsecs_t> getDownTimeInTarget(DeviceId deviceId) const;
+    void trySetDownTimeInTarget(DeviceId deviceId, nsecs_t downTime);
 
-    void removeAllTouchingPointersForDevice(int32_t deviceId);
-    void removeAllHoveringPointersForDevice(int32_t deviceId);
-    void clearHoveringPointers();
+    void removeAllTouchingPointersForDevice(DeviceId deviceId);
+    void removeAllHoveringPointersForDevice(DeviceId deviceId);
+    void clearHoveringPointers(DeviceId deviceId);
     std::string dump() const;
 
 private:
@@ -88,10 +88,12 @@
         bool hasPointers() const { return touchingPointerIds.any() || hoveringPointerIds.any(); };
     };
 
-    std::map<int32_t /*deviceId*/, DeviceState> mDeviceStates;
+    std::map<DeviceId, DeviceState> mDeviceStates;
 
     static std::string deviceStateToString(const TouchedWindow::DeviceState& state);
 };
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 6a07e59..d099b44 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -87,14 +87,6 @@
      */
     virtual std::unique_ptr<VerifiedInputEvent> verifyInputEvent(const InputEvent& event) = 0;
 
-    /* Sets the list of input windows per display.
-     *
-     * This method may be called on any thread (usually by the input manager).
-     */
-    virtual void setInputWindows(
-            const std::unordered_map<int32_t, std::vector<sp<gui::WindowInfoHandle>>>&
-                    handlesPerDisplay) = 0;
-
     /* Sets the focused application on the given display.
      *
      * This method may be called on any thread (usually by the input manager).
@@ -226,11 +218,10 @@
      */
     virtual void cancelCurrentTouch() = 0;
 
-    /**
-     * Request that the InputDispatcher's configuration, which can be obtained through the policy,
-     * be updated.
+    /*
+     * Updates key repeat configuration timeout and delay.
      */
-    virtual void requestRefreshConfiguration() = 0;
+    virtual void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 729d01f..af28e48 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -73,9 +73,6 @@
                                       InputDeviceSensorAccuracy accuracy) = 0;
     virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0;
 
-    /* Gets the input dispatcher configuration. */
-    virtual InputDispatcherConfiguration getDispatcherConfiguration() = 0;
-
     /* Filters an input event.
      * Return true to dispatch the event unmodified, false to consume the event.
      * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
diff --git a/services/inputflinger/host/Android.bp b/services/inputflinger/host/Android.bp
deleted file mode 100644
index 4d2839f..0000000
--- a/services/inputflinger/host/Android.bp
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_library_shared {
-    name: "libinputflingerhost",
-    cpp_std: "c++20",
-    srcs: [
-        "InputFlinger.cpp",
-        "InputDriver.cpp",
-        "InputHost.cpp",
-    ],
-
-    header_libs: ["jni_headers"],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "libcrypto",
-        "libcutils",
-        "libinput",
-        "liblog",
-        "libutils",
-        "libhardware",
-    ],
-    static_libs: [
-        "libarect",
-    ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        // TODO: Move inputflinger to its own process and mark it hidden
-        //-fvisibility=hidden
-    ],
-
-    export_header_lib_headers: ["jni_headers"],
-    export_include_dirs: ["."],
-}
-
-//#######################################################################
-// build input flinger executable
-cc_binary {
-    name: "inputflinger",
-
-    srcs: ["main.cpp"],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "libinputflingerhost",
-        "libutils",
-        "libinput",
-    ],
-    static_libs: [
-        "libarect",
-        "libui-types",
-    ],
-}
diff --git a/services/inputflinger/host/InputDriver.cpp b/services/inputflinger/host/InputDriver.cpp
deleted file mode 100644
index de99fc7..0000000
--- a/services/inputflinger/host/InputDriver.cpp
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <functional>
-#include <stdint.h>
-#include <sys/types.h>
-#include <unordered_map>
-#include <vector>
-
-#define LOG_TAG "InputDriver"
-
-#define LOG_NDEBUG 0
-
-#include "InputDriver.h"
-#include "InputHost.h"
-
-#include <hardware/input.h>
-#include <input/InputDevice.h>
-#include <input/PropertyMap.h>
-#include <utils/Log.h>
-#include <utils/String8.h>
-
-#define INDENT2 "    "
-
-struct input_property_map {
-    std::unique_ptr<android::PropertyMap> propertyMap;
-};
-
-struct input_property {
-    android::String8 key;
-    android::String8 value;
-};
-
-struct input_device_identifier {
-    const char* name;
-    const char* uniqueId;
-    input_bus_t bus;
-    int32_t     vendorId;
-    int32_t     productId;
-    int32_t     version;
-};
-
-struct input_device_definition {
-    std::vector<input_report_definition*> reportDefs;
-};
-
-struct input_device_handle {
-    input_device_identifier_t* id;
-    input_device_definition_t* def;
-};
-
-struct input_int_usage {
-    input_usage_t usage;
-    int32_t min;
-    int32_t max;
-    float   resolution;
-};
-
-struct input_collection {
-    int32_t arity;
-    std::vector<input_int_usage> intUsages;
-    std::vector<input_usage_t> boolUsages;
-};
-
-struct InputCollectionIdHasher {
-    std::size_t operator()(const input_collection_id& id) const {
-        return std::hash<int>()(static_cast<int>(id));
-    }
-};
-
-struct input_report_definition {
-    std::unordered_map<input_collection_id_t, input_collection, InputCollectionIdHasher> collections;
-};
-
-
-namespace android {
-
-static input_host_callbacks_t kCallbacks = {
-    .create_device_identifier = create_device_identifier,
-    .create_device_definition = create_device_definition,
-    .create_input_report_definition = create_input_report_definition,
-    .create_output_report_definition = create_output_report_definition,
-    .free_report_definition = free_report_definition,
-    .input_device_definition_add_report = input_device_definition_add_report,
-    .input_report_definition_add_collection = input_report_definition_add_collection,
-    .input_report_definition_declare_usage_int = input_report_definition_declare_usage_int,
-    .input_report_definition_declare_usages_bool = input_report_definition_declare_usages_bool,
-    .register_device = register_device,
-    .input_allocate_report = input_allocate_report,
-    .input_report_set_usage_int = input_report_set_usage_int,
-    .input_report_set_usage_bool = input_report_set_usage_bool,
-    .report_event = report_event,
-    .input_get_device_property_map = input_get_device_property_map,
-    .input_get_device_property = input_get_device_property,
-    .input_get_property_key = input_get_property_key,
-    .input_get_property_value = input_get_property_value,
-    .input_free_device_property = input_free_device_property,
-    .input_free_device_property_map = input_free_device_property_map,
-};
-
-InputDriver::InputDriver(const char* name) : mName(String8(name)) {
-    const hw_module_t* module;
-    int err = input_open(&module, name);
-    LOG_ALWAYS_FATAL_IF(err != 0, "Input module %s not found", name);
-    mHal = reinterpret_cast<const input_module_t*>(module);
-}
-
-void InputDriver::init() {
-    mHal->init(mHal, static_cast<input_host_t*>(this), kCallbacks);
-}
-
-input_device_identifier_t* InputDriver::createDeviceIdentifier(
-            const char* name, int32_t productId, int32_t vendorId,
-            input_bus_t bus, const char* uniqueId) {
-    auto identifier = new ::input_device_identifier {
-        .name = name,
-        .uniqueId = uniqueId,
-        .bus = bus,
-        .vendorId = vendorId,
-        .productId = productId,
-    };
-    // TODO: store this identifier somewhere
-    return identifier;
-}
-
-input_device_definition_t* InputDriver::createDeviceDefinition() {
-    return new ::input_device_definition;
-}
-
-input_report_definition_t* InputDriver::createInputReportDefinition() {
-    return new ::input_report_definition;
-}
-
-input_report_definition_t* InputDriver::createOutputReportDefinition() {
-    return new ::input_report_definition;
-}
-
-void InputDriver::freeReportDefinition(input_report_definition_t* reportDef) {
-    delete reportDef;
-}
-
-void InputDriver::inputDeviceDefinitionAddReport(input_device_definition_t* d,
-        input_report_definition_t* r) {
-    d->reportDefs.push_back(r);
-}
-
-void InputDriver::inputReportDefinitionAddCollection(input_report_definition_t* report,
-        input_collection_id_t id, int32_t arity) {
-    report->collections[id] = {.arity = arity};
-}
-
-void InputDriver::inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
-        input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
-        float resolution) {
-    if (report->collections.find(id) != report->collections.end()) {
-        report->collections[id].intUsages.push_back({
-                .usage = usage, .min = min, .max = max, .resolution = resolution});
-    }
-}
-
-void InputDriver::inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
-        input_collection_id_t id, input_usage_t* usage, size_t usageCount) {
-    if (report->collections.find(id) != report->collections.end()) {
-        for (size_t i = 0; i < usageCount; ++i) {
-            report->collections[id].boolUsages.push_back(usage[i]);
-        }
-    }
-}
-
-input_device_handle_t* InputDriver::registerDevice(input_device_identifier_t* id,
-        input_device_definition_t* d) {
-    ALOGD("Registering device %s with %zu input reports", id->name, d->reportDefs.size());
-    // TODO: save this device handle
-    return new input_device_handle{ .id = id, .def = d };
-}
-
-void InputDriver::unregisterDevice(input_device_handle_t* handle) {
-    delete handle;
-}
-
-input_report_t* InputDriver::inputAllocateReport(input_report_definition_t* r) {
-    ALOGD("Allocating input report for definition %p", r);
-    return nullptr;
-}
-
-void InputDriver::inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
-        input_usage_t usage, int32_t value, int32_t arity_index) {
-}
-
-void InputDriver::inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
-        input_usage_t usage, bool value, int32_t arity_index) {
-}
-
-void InputDriver::reportEvent(input_device_handle_t* d, input_report_t* report) {
-    ALOGD("report_event %p for handle %p", report, d);
-}
-
-input_property_map_t* InputDriver::inputGetDevicePropertyMap(input_device_identifier_t* id) {
-    InputDeviceIdentifier idi;
-    idi.name = id->name;
-    idi.uniqueId = id->uniqueId;
-    idi.bus = id->bus;
-    idi.vendor = id->vendorId;
-    idi.product = id->productId;
-    idi.version = id->version;
-
-    std::string configFile =
-            getInputDeviceConfigurationFilePathByDeviceIdentifier(idi,
-                                                                  InputDeviceConfigurationFileType::
-                                                                          CONFIGURATION);
-    if (configFile.empty()) {
-        ALOGD("No input device configuration file found for device '%s'.",
-                idi.name.c_str());
-    } else {
-        std::unique_ptr<input_property_map_t> propMap = std::make_unique<input_property_map_t>();
-        android::base::Result<std::unique_ptr<PropertyMap>> result =
-                PropertyMap::load(configFile.c_str());
-        if (!result.ok()) {
-            ALOGE("Error loading input device configuration file for device '%s'. "
-                    "Using default configuration.",
-                    idi.name.c_str());
-            return nullptr;
-        }
-        propMap->propertyMap = std::move(*result);
-        return propMap.release();
-    }
-    return nullptr;
-}
-
-input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map, const char* key) {
-    if (map != nullptr) {
-        std::optional<std::string> value = map->propertyMap->getString(key);
-        if (!value.has_value()) {
-            return nullptr;
-        }
-        auto prop = std::make_unique<input_property_t>();
-        prop->key = key;
-        prop->value = value->c_str();
-        return prop.release();
-    }
-    return nullptr;
-}
-
-const char* InputDriver::inputGetPropertyKey(input_property_t* property) {
-    if (property != nullptr) {
-        return property->key.c_str();
-    }
-    return nullptr;
-}
-
-const char* InputDriver::inputGetPropertyValue(input_property_t* property) {
-    if (property != nullptr) {
-        return property->value.c_str();
-    }
-    return nullptr;
-}
-
-void InputDriver::inputFreeDeviceProperty(input_property_t* property) {
-    if (property != nullptr) {
-        delete property;
-    }
-}
-
-void InputDriver::inputFreeDevicePropertyMap(input_property_map_t* map) {
-    if (map != nullptr) {
-        delete map;
-    }
-}
-
-void InputDriver::dump(String8& result) {
-    result.appendFormat(INDENT2 "HAL Input Driver (%s)\n", mName.c_str());
-}
-
-} // namespace android
-
-// HAL wrapper functions
-
-namespace android {
-
-::input_device_identifier_t* create_device_identifier(input_host_t* host,
-        const char* name, int32_t product_id, int32_t vendor_id,
-        input_bus_t bus, const char* unique_id) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createDeviceIdentifier(name, product_id, vendor_id, bus, unique_id);
-}
-
-input_device_definition_t* create_device_definition(input_host_t* host) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createDeviceDefinition();
-}
-
-input_report_definition_t* create_input_report_definition(input_host_t* host) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createInputReportDefinition();
-}
-
-input_report_definition_t* create_output_report_definition(input_host_t* host) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createOutputReportDefinition();
-}
-
-void free_report_definition(input_host_t* host, input_report_definition_t* report_def) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->freeReportDefinition(report_def);
-}
-
-void input_device_definition_add_report(input_host_t* host,
-        input_device_definition_t* d, input_report_definition_t* r) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputDeviceDefinitionAddReport(d, r);
-}
-
-void input_report_definition_add_collection(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id, int32_t arity) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportDefinitionAddCollection(report, id, arity);
-}
-
-void input_report_definition_declare_usage_int(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t usage, int32_t min, int32_t max, float resolution) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportDefinitionDeclareUsageInt(report, id, usage, min, max, resolution);
-}
-
-void input_report_definition_declare_usages_bool(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t* usage, size_t usage_count) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportDefinitionDeclareUsagesBool(report, id, usage, usage_count);
-}
-
-input_device_handle_t* register_device(input_host_t* host,
-        input_device_identifier_t* id, input_device_definition_t* d) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->registerDevice(id, d);
-}
-
-void unregister_device(input_host_t* host, input_device_handle_t* handle) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->unregisterDevice(handle);
-}
-
-input_report_t* input_allocate_report(input_host_t* host, input_report_definition_t* r) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputAllocateReport(r);
-}
-
-void input_report_set_usage_int(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, int32_t value, int32_t arity_index) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportSetUsageInt(r, id, usage, value, arity_index);
-}
-
-void input_report_set_usage_bool(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, bool value, int32_t arity_index) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportSetUsageBool(r, id, usage, value, arity_index);
-}
-
-void report_event(input_host_t* host, input_device_handle_t* d, input_report_t* report) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->reportEvent(d, report);
-}
-
-input_property_map_t* input_get_device_property_map(input_host_t* host,
-        input_device_identifier_t* id) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetDevicePropertyMap(id);
-}
-
-input_property_t* input_get_device_property(input_host_t* host, input_property_map_t* map,
-        const char* key) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetDeviceProperty(map, key);
-}
-
-const char* input_get_property_key(input_host_t* host, input_property_t* property) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetPropertyKey(property);
-}
-
-const char* input_get_property_value(input_host_t* host, input_property_t* property) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetPropertyValue(property);
-}
-
-void input_free_device_property(input_host_t* host, input_property_t* property) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputFreeDeviceProperty(property);
-}
-
-void input_free_device_property_map(input_host_t* host, input_property_map_t* map) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputFreeDevicePropertyMap(map);
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputDriver.h b/services/inputflinger/host/InputDriver.h
deleted file mode 100644
index b4c9094..0000000
--- a/services/inputflinger/host/InputDriver.h
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "InputHost.h"
-
-#include <hardware/input.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-
-// Declare a concrete type for the HAL
-struct input_host {
-};
-
-namespace android {
-
-class InputDriverInterface : public input_host_t, public virtual RefBase {
-protected:
-    InputDriverInterface() = default;
-    virtual ~InputDriverInterface() = default;
-
-public:
-    virtual void init() = 0;
-
-    virtual input_device_identifier_t* createDeviceIdentifier(
-            const char* name, int32_t productId, int32_t vendorId,
-            input_bus_t bus, const char* uniqueId) = 0;
-    virtual input_device_definition_t* createDeviceDefinition() = 0;
-    virtual input_report_definition_t* createInputReportDefinition() = 0;
-    virtual input_report_definition_t* createOutputReportDefinition() = 0;
-    virtual void freeReportDefinition(input_report_definition_t* reportDef) = 0;
-
-    virtual void inputDeviceDefinitionAddReport(input_device_definition_t* d,
-            input_report_definition_t* r) = 0;
-    virtual void inputReportDefinitionAddCollection(input_report_definition_t* report,
-            input_collection_id_t id, int32_t arity) = 0;
-    virtual void inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
-            float resolution) = 0;
-    virtual void inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t* usage, size_t usageCount) = 0;
-
-    virtual input_device_handle_t* registerDevice(input_device_identifier_t* id,
-            input_device_definition_t* d) = 0;
-    virtual void unregisterDevice(input_device_handle_t* handle) = 0;
-
-    virtual input_report_t* inputAllocateReport(input_report_definition_t* r) = 0;
-    virtual void inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, int32_t value, int32_t arity_index) = 0;
-    virtual void inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, bool value, int32_t arity_index) = 0;
-    virtual void reportEvent(input_device_handle_t* d, input_report_t* report) = 0;
-
-    virtual input_property_map_t* inputGetDevicePropertyMap(input_device_identifier_t* id) = 0;
-    virtual input_property_t* inputGetDeviceProperty(input_property_map_t* map,
-            const char* key) = 0;
-    virtual const char* inputGetPropertyKey(input_property_t* property) = 0;
-    virtual const char* inputGetPropertyValue(input_property_t* property) = 0;
-    virtual void inputFreeDeviceProperty(input_property_t* property) = 0;
-    virtual void inputFreeDevicePropertyMap(input_property_map_t* map) = 0;
-
-    virtual void dump(String8& result) = 0;
-};
-
-class InputDriver : public InputDriverInterface {
-public:
-    explicit InputDriver(const char* name);
-    virtual ~InputDriver() = default;
-
-    virtual void init() override;
-
-    virtual input_device_identifier_t* createDeviceIdentifier(
-            const char* name, int32_t productId, int32_t vendorId,
-            input_bus_t bus, const char* uniqueId) override;
-    virtual input_device_definition_t* createDeviceDefinition() override;
-    virtual input_report_definition_t* createInputReportDefinition() override;
-    virtual input_report_definition_t* createOutputReportDefinition() override;
-    virtual void freeReportDefinition(input_report_definition_t* reportDef) override;
-
-    virtual void inputDeviceDefinitionAddReport(input_device_definition_t* d,
-            input_report_definition_t* r) override;
-    virtual void inputReportDefinitionAddCollection(input_report_definition_t* report,
-            input_collection_id_t id, int32_t arity) override;
-    virtual void inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
-            float resolution) override;
-    virtual void inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t* usage, size_t usageCount) override;
-
-    virtual input_device_handle_t* registerDevice(input_device_identifier_t* id,
-            input_device_definition_t* d) override;
-    virtual void unregisterDevice(input_device_handle_t* handle) override;
-
-    virtual input_report_t* inputAllocateReport(input_report_definition_t* r) override;
-    virtual void inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, int32_t value, int32_t arity_index) override;
-    virtual void inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, bool value, int32_t arity_index) override;
-    virtual void reportEvent(input_device_handle_t* d, input_report_t* report) override;
-
-    virtual input_property_map_t* inputGetDevicePropertyMap(input_device_identifier_t* id) override;
-    virtual input_property_t* inputGetDeviceProperty(input_property_map_t* map,
-            const char* key) override;
-    virtual const char* inputGetPropertyKey(input_property_t* property) override;
-    virtual const char* inputGetPropertyValue(input_property_t* property) override;
-    virtual void inputFreeDeviceProperty(input_property_t* property) override;
-    virtual void inputFreeDevicePropertyMap(input_property_map_t* map) override;
-
-    virtual void dump(String8& result) override;
-
-private:
-    String8 mName;
-    const input_module_t* mHal;
-};
-
-
-extern "C" {
-
-input_device_identifier_t* create_device_identifier(input_host_t* host,
-        const char* name, int32_t product_id, int32_t vendor_id,
-        input_bus_t bus, const char* unique_id);
-
-input_device_definition_t* create_device_definition(input_host_t* host);
-
-input_report_definition_t* create_input_report_definition(input_host_t* host);
-
-input_report_definition_t* create_output_report_definition(input_host_t* host);
-
-void free_report_definition(input_host_t* host, input_report_definition_t* report_def);
-
-void input_device_definition_add_report(input_host_t* host,
-        input_device_definition_t* d, input_report_definition_t* r);
-
-void input_report_definition_add_collection(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id, int32_t arity);
-
-void input_report_definition_declare_usage_int(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t usage, int32_t min, int32_t max, float resolution);
-
-void input_report_definition_declare_usages_bool(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t* usage, size_t usage_count);
-
-
-input_device_handle_t* register_device(input_host_t* host,
-        input_device_identifier_t* id, input_device_definition_t* d);
-
-void unregister_device(input_host_t* host, input_device_handle_t* handle);
-
-input_report_t* input_allocate_report(input_host_t* host, input_report_definition_t* r);
-
-void input_report_set_usage_int(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, int32_t value, int32_t arity_index);
-
-void input_report_set_usage_bool(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, bool value, int32_t arity_index);
-
-void report_event(input_host_t* host, input_device_handle_t* d, input_report_t* report);
-
-input_property_map_t* input_get_device_property_map(input_host_t* host,
-        input_device_identifier_t* id);
-
-input_property_t* input_get_device_property(input_host_t* host, input_property_map_t* map,
-        const char* key);
-
-const char* input_get_property_key(input_host_t* host, input_property_t* property);
-
-const char* input_get_property_value(input_host_t* host, input_property_t* property);
-
-void input_free_device_property(input_host_t* host, input_property_t* property);
-
-void input_free_device_property_map(input_host_t* host, input_property_map_t* map);
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputFlinger.cpp b/services/inputflinger/host/InputFlinger.cpp
deleted file mode 100644
index d974c43..0000000
--- a/services/inputflinger/host/InputFlinger.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "InputFlinger"
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionCache.h>
-#include <hardware/input.h>
-#include <log/log.h>
-#include <private/android_filesystem_config.h>
-
-#include "InputFlinger.h"
-#include "InputDriver.h"
-
-namespace android {
-
-const String16 sAccessInputFlingerPermission("android.permission.ACCESS_INPUT_FLINGER");
-const String16 sDumpPermission("android.permission.DUMP");
-
-
-InputFlinger::InputFlinger() :
-        BnInputFlinger() {
-    ALOGI("InputFlinger is starting");
-    mHost = new InputHost();
-    mHost->registerInputDriver(new InputDriver(INPUT_INSTANCE_EVDEV));
-}
-
-InputFlinger::~InputFlinger() {
-}
-
-status_t InputFlinger::dump(int fd, const Vector<String16>& args) {
-    String8 result;
-    const IPCThreadState* ipc = IPCThreadState::self();
-    const int pid = ipc->getCallingPid();
-    const int uid = ipc->getCallingUid();
-    if ((uid != AID_SHELL)
-            && !PermissionCache::checkPermission(sDumpPermission, pid, uid)) {
-        result.appendFormat("Permission Denial: "
-                "can't dump InputFlinger from pid=%d, uid=%d\n", pid, uid);
-    } else {
-        dumpInternal(result);
-    }
-    write(fd, result.c_str(), result.size());
-    return OK;
-}
-
-void InputFlinger::dumpInternal(String8& result) {
-    result.append("INPUT FLINGER (dumpsys inputflinger)\n");
-    mHost->dump(result);
-}
-
-}; // namespace android
diff --git a/services/inputflinger/host/InputFlinger.h b/services/inputflinger/host/InputFlinger.h
deleted file mode 100644
index 388988b..0000000
--- a/services/inputflinger/host/InputFlinger.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "InputHost.h"
-
-#include <android/os/BnInputFlinger.h>
-#include <binder/Binder.h>
-#include <cutils/compiler.h>
-#include <utils/String16.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-using android::gui::FocusRequest;
-using android::os::BnInputFlinger;
-
-namespace android {
-
-class InputFlinger : public BnInputFlinger {
-public:
-    static char const* getServiceName() ANDROID_API {
-        return "inputflinger";
-    }
-
-    InputFlinger() ANDROID_API;
-
-    status_t dump(int fd, const Vector<String16>& args) override;
-    binder::Status createInputChannel(const std::string&, InputChannel*) override {
-        return binder::Status::ok();
-    }
-    binder::Status removeInputChannel(const sp<IBinder>&) override { return binder::Status::ok(); }
-    binder::Status setFocusedWindow(const FocusRequest&) override { return binder::Status::ok(); }
-
-private:
-    ~InputFlinger() override;
-
-    void dumpInternal(String8& result);
-
-    sp<InputHostInterface> mHost;
-};
-
-} // namespace android
diff --git a/services/inputflinger/host/InputHost.cpp b/services/inputflinger/host/InputHost.cpp
deleted file mode 100644
index 094200a..0000000
--- a/services/inputflinger/host/InputHost.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <vector>
-
-#include "InputDriver.h"
-#include "InputHost.h"
-
-#include <utils/Log.h>
-#include <utils/String8.h>
-
-#define INDENT "  "
-
-namespace android {
-
-void InputHost::registerInputDriver(InputDriverInterface* driver) {
-    LOG_ALWAYS_FATAL_IF(driver == nullptr, "Cannot register a nullptr as an InputDriver!");
-    driver->init();
-    mDrivers.push_back(driver);
-}
-
-void InputHost::dump(String8& result) {
-    result.append(INDENT "Input Drivers:\n");
-    for (size_t i = 0; i < mDrivers.size(); i++) {
-        mDrivers[i]->dump(result);
-    }
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputHost.h b/services/inputflinger/host/InputHost.h
deleted file mode 100644
index bdc4225..0000000
--- a/services/inputflinger/host/InputHost.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <vector>
-
-#include <hardware/input.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-#include "InputDriver.h"
-
-namespace android {
-
-class InputDriverInterface;
-
-class InputHostInterface : public virtual RefBase {
-protected:
-    InputHostInterface() = default;
-    virtual ~InputHostInterface() = default;
-
-public:
-
-    virtual void registerInputDriver(InputDriverInterface* driver) = 0;
-
-    virtual void dump(String8& result) = 0;
-};
-
-class InputHost : public InputHostInterface {
-public:
-    InputHost() = default;
-
-    virtual void registerInputDriver(InputDriverInterface* driver) override;
-
-    virtual void dump(String8& result) override;
-
-private:
-    std::vector<sp<InputDriverInterface>> mDrivers;
-};
-
-} // namespace android
diff --git a/services/inputflinger/host/inputflinger.rc b/services/inputflinger/host/inputflinger.rc
deleted file mode 100644
index 4130ddc..0000000
--- a/services/inputflinger/host/inputflinger.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service inputflinger /system/bin/inputflinger
-    class main
-    user system
-    group input wakelock
-#    onrestart restart zygote
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index 4f78f03..0b7f7c2 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -76,4 +76,26 @@
     std::vector<NotifyArgs> mArgsQueue;
 };
 
+/*
+ * An implementation of the listener interface that traces the calls to its inner listener.
+ */
+class TracedInputListener : public InputListenerInterface {
+public:
+    explicit TracedInputListener(const char* name, InputListenerInterface& innerListener);
+
+    virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
+    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    virtual void notifyKey(const NotifyKeyArgs& args) override;
+    virtual void notifyMotion(const NotifyMotionArgs& args) override;
+    virtual void notifySwitch(const NotifySwitchArgs& args) override;
+    virtual void notifySensor(const NotifySensorArgs& args) override;
+    virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
+
+private:
+    InputListenerInterface& mInnerListener;
+    const char* mName;
+};
+
 } // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 88e1d7d..25e1d21 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -437,7 +437,8 @@
 
     /* Gets the keyboard layout for a particular input device. */
     virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
-            const InputDeviceIdentifier& identifier) = 0;
+            const InputDeviceIdentifier& identifier,
+            const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) = 0;
 
     /* Gets a user-supplied alias for a particular input device, or an empty string if none. */
     virtual std::string getDeviceAlias(const InputDeviceIdentifier& identifier) = 0;
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
new file mode 100644
index 0000000..9e020c7
--- /dev/null
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "PointerControllerInterface.h"
+
+namespace android {
+
+/**
+ * The PointerChoreographer policy interface.
+ *
+ * This is the interface that PointerChoreographer uses to talk to Window Manager and other
+ * system components.
+ */
+class PointerChoreographerPolicyInterface {
+public:
+    virtual ~PointerChoreographerPolicyInterface() = default;
+
+    /**
+     * A factory method for PointerController. The PointerController implementation has
+     * dependencies on a graphical library - libgui, used to draw icons on the screen - which
+     * isn't available for the host. Since we want libinputflinger and its test to be buildable
+     * for and runnable on the host, the PointerController implementation must be in a separate
+     * library, libinputservice, that has the additional dependencies. The PointerController
+     * will be mocked when testing PointerChoreographer.
+     */
+    virtual std::shared_ptr<PointerControllerInterface> createPointerController() = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index ccb8773..e1806a0 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -52,6 +52,7 @@
         "mapper/RotaryEncoderInputMapper.cpp",
         "mapper/SensorInputMapper.cpp",
         "mapper/SingleTouchInputMapper.cpp",
+        "mapper/SlopController.cpp",
         "mapper/SwitchInputMapper.cpp",
         "mapper/TouchCursorInputMapperCommon.cpp",
         "mapper/TouchInputMapper.cpp",
@@ -65,8 +66,10 @@
         "mapper/accumulator/TouchButtonAccumulator.cpp",
         "mapper/gestures/GestureConverter.cpp",
         "mapper/gestures/GesturesLogging.cpp",
+        "mapper/gestures/HardwareProperties.cpp",
         "mapper/gestures/HardwareStateConverter.cpp",
         "mapper/gestures/PropertyProvider.cpp",
+        "mapper/gestures/TimerProvider.cpp",
     ],
 }
 
@@ -79,6 +82,7 @@
         "libcrypto",
         "libcutils",
         "libjsoncpp",
+        "libinput",
         "liblog",
         "libPlatformProperties",
         "libstatslog",
@@ -97,13 +101,11 @@
     target: {
         android: {
             shared_libs: [
-                "libinput",
                 "libstatspull",
             ],
         },
         host: {
             static_libs: [
-                "libinput",
                 "libbinder",
                 "libstatspull",
             ],
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 44e80a7..f7bbc51 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -538,7 +538,8 @@
         associatedDevice(std::move(assocDev)),
         controllerNumber(0),
         enabled(true),
-        isVirtual(fd < 0) {}
+        isVirtual(fd < 0),
+        currentFrameDropped(false) {}
 
 EventHub::Device::~Device() {
     close();
@@ -612,6 +613,45 @@
     }
     bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
     ALOGI("usingClockIoctl=%s", toString(usingClockIoctl));
+
+    // Query the initial state of keys and switches, which is tracked by EventHub.
+    readDeviceState();
+}
+
+void EventHub::Device::readDeviceState() {
+    if (readDeviceBitMask(EVIOCGKEY(0), keyState) < 0) {
+        ALOGD("Unable to query the global key state for %s: %s", path.c_str(), strerror(errno));
+    }
+    if (readDeviceBitMask(EVIOCGSW(0), swState) < 0) {
+        ALOGD("Unable to query the global switch state for %s: %s", path.c_str(), strerror(errno));
+    }
+
+    // Read absolute axis info and values for all available axes for the device.
+    populateAbsoluteAxisStates();
+}
+
+void EventHub::Device::populateAbsoluteAxisStates() {
+    absState.clear();
+
+    for (int axis = 0; axis <= ABS_MAX; axis++) {
+        if (!absBitmask.test(axis)) {
+            continue;
+        }
+        struct input_absinfo info {};
+        if (ioctl(fd, EVIOCGABS(axis), &info)) {
+            ALOGE("Error reading absolute controller %d for device %s fd %d: %s", axis,
+                  identifier.name.c_str(), fd, strerror(errno));
+            continue;
+        }
+        auto& [axisInfo, value] = absState[axis];
+        axisInfo.valid = true;
+        axisInfo.minValue = info.minimum;
+        axisInfo.maxValue = info.maximum;
+        axisInfo.flat = info.flat;
+        axisInfo.fuzz = info.fuzz;
+        axisInfo.resolution = info.resolution;
+        value = info.value;
+    }
 }
 
 bool EventHub::Device::hasKeycodeLocked(int keycode) const {
@@ -729,6 +769,66 @@
     return NAME_NOT_FOUND;
 }
 
+void EventHub::Device::trackInputEvent(const struct input_event& event) {
+    switch (event.type) {
+        case EV_KEY: {
+            LOG_ALWAYS_FATAL_IF(!currentFrameDropped &&
+                                        !keyState.set(static_cast<size_t>(event.code),
+                                                      event.value != 0),
+                                "%s: device '%s' received invalid EV_KEY event code: %s value: %d",
+                                __func__, identifier.name.c_str(),
+                                InputEventLookup::getLinuxEvdevLabel(EV_KEY, event.code, 1)
+                                        .code.c_str(),
+                                event.value);
+            break;
+        }
+        case EV_SW: {
+            LOG_ALWAYS_FATAL_IF(!currentFrameDropped &&
+                                        !swState.set(static_cast<size_t>(event.code),
+                                                     event.value != 0),
+                                "%s: device '%s' received invalid EV_SW event code: %s value: %d",
+                                __func__, identifier.name.c_str(),
+                                InputEventLookup::getLinuxEvdevLabel(EV_SW, event.code, 1)
+                                        .code.c_str(),
+                                event.value);
+            break;
+        }
+        case EV_ABS: {
+            if (currentFrameDropped) {
+                break;
+            }
+            auto it = absState.find(event.code);
+            LOG_ALWAYS_FATAL_IF(it == absState.end(),
+                                "%s: device '%s' received invalid EV_ABS event code: %s value: %d",
+                                __func__, identifier.name.c_str(),
+                                InputEventLookup::getLinuxEvdevLabel(EV_ABS, event.code, 0)
+                                        .code.c_str(),
+                                event.value);
+            it->second.value = event.value;
+            break;
+        }
+        case EV_SYN: {
+            switch (event.code) {
+                case SYN_REPORT:
+                    currentFrameDropped = false;
+                    break;
+                case SYN_DROPPED:
+                    // When we receive SYN_DROPPED, all events in the current frame should be
+                    // dropped. We query the state of the device to synchronize our device state
+                    // with the kernel's to account for the dropped events.
+                    currentFrameDropped = true;
+                    readDeviceState();
+                    break;
+                default:
+                    break;
+            }
+            break;
+        }
+        default:
+            break;
+    }
+}
+
 /**
  * Get the capabilities for the current process.
  * Crashes the system if unable to create / check / destroy the capabilities object.
@@ -864,30 +964,6 @@
                         strerror(errno));
 }
 
-void EventHub::populateDeviceAbsoluteAxisInfo(Device& device) {
-    for (int axis = 0; axis <= ABS_MAX; axis++) {
-        if (!device.absBitmask.test(axis)) {
-            continue;
-        }
-        struct input_absinfo info {};
-        if (ioctl(device.fd, EVIOCGABS(axis), &info)) {
-            ALOGE("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
-                  device.identifier.name.c_str(), device.fd, errno);
-            continue;
-        }
-        if (info.minimum == info.maximum) {
-            continue;
-        }
-        RawAbsoluteAxisInfo& outAxisInfo = device.rawAbsoluteAxisInfoCache[axis];
-        outAxisInfo.valid = true;
-        outAxisInfo.minValue = info.minimum;
-        outAxisInfo.maxValue = info.maximum;
-        outAxisInfo.flat = info.flat;
-        outAxisInfo.fuzz = info.fuzz;
-        outAxisInfo.resolution = info.resolution;
-    }
-}
-
 InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
@@ -919,18 +995,21 @@
                                        RawAbsoluteAxisInfo* outAxisInfo) const {
     outAxisInfo->clear();
     if (axis < 0 || axis > ABS_MAX) {
-        return -1;
+        return NAME_NOT_FOUND;
     }
     std::scoped_lock _l(mLock);
-    Device* device = getDeviceLocked(deviceId);
+    const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
-        return -1;
+        return NAME_NOT_FOUND;
     }
-    auto it = device->rawAbsoluteAxisInfoCache.find(axis);
-    if (it == device->rawAbsoluteAxisInfoCache.end()) {
-        return -1;
+    // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
+    // fd, because the info is populated once when the device is first opened, and it doesn't change
+    // throughout the device lifecycle.
+    auto it = device->absState.find(axis);
+    if (it == device->absState.end()) {
+        return NAME_NOT_FOUND;
     }
-    *outAxisInfo = it->second;
+    *outAxisInfo = it->second.info;
     return OK;
 }
 
@@ -962,38 +1041,34 @@
 }
 
 int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
-    if (scanCode >= 0 && scanCode <= KEY_MAX) {
-        std::scoped_lock _l(mLock);
-
-        Device* device = getDeviceLocked(deviceId);
-        if (device != nullptr && device->hasValidFd() && device->keyBitmask.test(scanCode)) {
-            if (device->readDeviceBitMask(EVIOCGKEY(0), device->keyState) >= 0) {
-                return device->keyState.test(scanCode) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
-            }
-        }
+    if (scanCode < 0 || scanCode > KEY_MAX) {
+        return AKEY_STATE_UNKNOWN;
     }
-    return AKEY_STATE_UNKNOWN;
+    std::scoped_lock _l(mLock);
+    const Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || !device->keyBitmask.test(scanCode)) {
+        return AKEY_STATE_UNKNOWN;
+    }
+    return device->keyState.test(scanCode) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
 }
 
 int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
     std::scoped_lock _l(mLock);
-
-    Device* device = getDeviceLocked(deviceId);
-    if (device != nullptr && device->hasValidFd() && device->keyMap.haveKeyLayout()) {
-        std::vector<int32_t> scanCodes = device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode);
-        if (scanCodes.size() != 0) {
-            if (device->readDeviceBitMask(EVIOCGKEY(0), device->keyState) >= 0) {
-                for (size_t i = 0; i < scanCodes.size(); i++) {
-                    int32_t sc = scanCodes[i];
-                    if (sc >= 0 && sc <= KEY_MAX && device->keyState.test(sc)) {
-                        return AKEY_STATE_DOWN;
-                    }
-                }
-                return AKEY_STATE_UP;
-            }
-        }
+    const Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || !device->keyMap.haveKeyLayout()) {
+        return AKEY_STATE_UNKNOWN;
     }
-    return AKEY_STATE_UNKNOWN;
+    const std::vector<int32_t> scanCodes =
+            device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode);
+    if (scanCodes.empty()) {
+        return AKEY_STATE_UNKNOWN;
+    }
+    return std::any_of(scanCodes.begin(), scanCodes.end(),
+                       [&device](const int32_t sc) {
+                           return sc >= 0 && sc <= KEY_MAX && device->keyState.test(sc);
+                       })
+            ? AKEY_STATE_DOWN
+            : AKEY_STATE_UP;
 }
 
 int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
@@ -1037,39 +1112,33 @@
 }
 
 int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
-    if (sw >= 0 && sw <= SW_MAX) {
-        std::scoped_lock _l(mLock);
-
-        Device* device = getDeviceLocked(deviceId);
-        if (device != nullptr && device->hasValidFd() && device->swBitmask.test(sw)) {
-            if (device->readDeviceBitMask(EVIOCGSW(0), device->swState) >= 0) {
-                return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
-            }
-        }
+    if (sw < 0 || sw > SW_MAX) {
+        return AKEY_STATE_UNKNOWN;
     }
-    return AKEY_STATE_UNKNOWN;
+    std::scoped_lock _l(mLock);
+    const Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || !device->swBitmask.test(sw)) {
+        return AKEY_STATE_UNKNOWN;
+    }
+    return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
 }
 
 status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
     *outValue = 0;
-
-    if (axis >= 0 && axis <= ABS_MAX) {
-        std::scoped_lock _l(mLock);
-
-        Device* device = getDeviceLocked(deviceId);
-        if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
-            struct input_absinfo info;
-            if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
-                ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
-                      device->identifier.name.c_str(), device->fd, errno);
-                return -errno;
-            }
-
-            *outValue = info.value;
-            return OK;
-        }
+    if (axis < 0 || axis > ABS_MAX) {
+        return NAME_NOT_FOUND;
     }
-    return -1;
+    std::scoped_lock _l(mLock);
+    const Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd()) {
+        return NAME_NOT_FOUND;
+    }
+    const auto it = device->absState.find(axis);
+    if (it == device->absState.end()) {
+        return NAME_NOT_FOUND;
+    }
+    *outValue = it->second.value;
+    return OK;
 }
 
 bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
@@ -1922,6 +1991,7 @@
                     const size_t count = size_t(readSize) / sizeof(struct input_event);
                     for (size_t i = 0; i < count; i++) {
                         struct input_event& iev = readBuffer[i];
+                        device->trackInputEvent(iev);
                         events.push_back({
                                 .when = processEventTimestamp(iev),
                                 .readTime = systemTime(SYSTEM_TIME_MONOTONIC),
@@ -2449,9 +2519,6 @@
 
     device->configureFd();
 
-    // read absolute axis info for all available axes for the device
-    populateDeviceAbsoluteAxisInfo(*device);
-
     ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=%s, "
           "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, ",
           deviceId, fd, devicePath.c_str(), device->identifier.name.c_str(),
@@ -2803,6 +2870,31 @@
                                  device->associatedDevice
                                          ? device->associatedDevice->sysfsRootPath.c_str()
                                          : "<none>");
+            if (device->keyBitmask.any(0, KEY_MAX + 1)) {
+                const auto pressedKeys = device->keyState.dumpSetIndices(", ", [](int i) {
+                    return InputEventLookup::getLinuxEvdevLabel(EV_KEY, i, 1).code;
+                });
+                dump += StringPrintf(INDENT3 "KeyState (pressed): %s\n", pressedKeys.c_str());
+            }
+            if (device->swBitmask.any(0, SW_MAX + 1)) {
+                const auto pressedSwitches = device->swState.dumpSetIndices(", ", [](int i) {
+                    return InputEventLookup::getLinuxEvdevLabel(EV_SW, i, 1).code;
+                });
+                dump += StringPrintf(INDENT3 "SwState (pressed): %s\n", pressedSwitches.c_str());
+            }
+            if (!device->absState.empty()) {
+                std::string axisValues;
+                for (const auto& [axis, state] : device->absState) {
+                    if (!axisValues.empty()) {
+                        axisValues += ", ";
+                    }
+                    axisValues += StringPrintf("%s=%d",
+                                               InputEventLookup::getLinuxEvdevLabel(EV_ABS, axis, 0)
+                                                       .code.c_str(),
+                                               state.value);
+                }
+                dump += INDENT3 "AbsState: " + axisValues + "\n";
+            }
         }
 
         dump += INDENT "Unattached video devices:\n";
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 0a64a1c..fb32f96 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -48,6 +48,7 @@
         mIdentifier(identifier),
         mClasses(0),
         mSources(0),
+        mIsWaking(false),
         mIsExternal(false),
         mHasMic(false),
         mDropUntilNextSync(false) {}
@@ -65,23 +66,38 @@
     return enabled;
 }
 
-std::list<NotifyArgs> InputDevice::setEnabled(bool enabled, nsecs_t when) {
-    std::list<NotifyArgs> out;
-    if (enabled && mAssociatedDisplayPort && !mAssociatedViewport) {
-        ALOGW("Cannot enable input device %s because it is associated with port %" PRIu8 ", "
-              "but the corresponding viewport is not found",
-              getName().c_str(), *mAssociatedDisplayPort);
-        enabled = false;
+std::list<NotifyArgs> InputDevice::updateEnableState(nsecs_t when,
+                                                     const InputReaderConfiguration& readerConfig,
+                                                     bool forceEnable) {
+    bool enable = forceEnable;
+    if (!forceEnable) {
+        // If the device was explicitly disabled by the user, it would be present in the
+        // "disabledDevices" list. This device should be disabled.
+        enable = readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end();
+
+        // If a device is associated with a specific display but there is no
+        // associated DisplayViewport, don't enable the device.
+        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+            !mAssociatedViewport) {
+            const std::string desc = mAssociatedDisplayPort
+                    ? "port " + std::to_string(*mAssociatedDisplayPort)
+                    : "uniqueId " + *mAssociatedDisplayUniqueId;
+            ALOGW("Cannot enable input device %s because it is associated "
+                  "with %s, but the corresponding viewport is not found",
+                  getName().c_str(), desc.c_str());
+            enable = false;
+        }
     }
 
-    if (isEnabled() == enabled) {
+    std::list<NotifyArgs> out;
+    if (isEnabled() == enable) {
         return out;
     }
 
     // When resetting some devices, the driver needs to be queried to ensure that a proper reset is
     // performed. The querying must happen when the device is enabled, so we reset after enabling
     // but before disabling the device. See MultiTouchMotionAccumulator::reset for more information.
-    if (enabled) {
+    if (enable) {
         for_each_subdevice([](auto& context) { context.enableDevice(); });
         out += reset(when);
     } else {
@@ -101,6 +117,7 @@
     dump += StringPrintf(INDENT "%s", eventHubDevStr.c_str());
     dump += StringPrintf(INDENT2 "Generation: %d\n", mGeneration);
     dump += StringPrintf(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
+    dump += StringPrintf(INDENT2 "IsWaking: %s\n", toString(mIsWaking));
     dump += StringPrintf(INDENT2 "AssociatedDisplayPort: ");
     if (mAssociatedDisplayPort) {
         dump += StringPrintf("%" PRIu8 "\n", *mAssociatedDisplayPort);
@@ -156,18 +173,25 @@
     mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
 }
 
-void InputDevice::addEventHubDevice(int32_t eventHubId,
-                                    const InputReaderConfiguration& readerConfig) {
+[[nodiscard]] std::list<NotifyArgs> InputDevice::addEventHubDevice(
+        nsecs_t when, int32_t eventHubId, const InputReaderConfiguration& readerConfig) {
     if (mDevices.find(eventHubId) != mDevices.end()) {
-        return;
+        return {};
     }
-    std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
-    std::vector<std::unique_ptr<InputMapper>> mappers = createMappers(*contextPtr, readerConfig);
 
-    // insert the context into the devices set
-    mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+    // Add an empty device configure and keep it enabled to allow mapper population with correct
+    // configuration/context,
+    // Note: we need to ensure device is kept enabled till mappers are configured
+    // TODO: b/281852638 refactor tests to remove this flag and reliance on the empty device
+    addEmptyEventHubDevice(eventHubId);
+    std::list<NotifyArgs> out = configureInternal(when, readerConfig, {}, /*forceEnable=*/true);
+
+    DevicePair& devicePair = mDevices[eventHubId];
+    devicePair.second = createMappers(*devicePair.first, readerConfig);
+
     // Must change generation to flag this device as changed
     bumpGeneration();
+    return out;
 }
 
 void InputDevice::removeEventHubDevice(int32_t eventHubId) {
@@ -181,6 +205,12 @@
 std::list<NotifyArgs> InputDevice::configure(nsecs_t when,
                                              const InputReaderConfiguration& readerConfig,
                                              ConfigurationChanges changes) {
+    return configureInternal(when, readerConfig, changes);
+}
+std::list<NotifyArgs> InputDevice::configureInternal(nsecs_t when,
+                                                     const InputReaderConfiguration& readerConfig,
+                                                     ConfigurationChanges changes,
+                                                     bool forceEnable) {
     std::list<NotifyArgs> out;
     mSources = 0;
     mClasses = ftl::Flags<InputDeviceClass>(0);
@@ -220,23 +250,7 @@
 
             mAssociatedDeviceType =
                     getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location);
-        }
-
-        if (!changes.any() || changes.test(Change::KEYBOARD_LAYOUTS)) {
-            if (!mClasses.test(InputDeviceClass::VIRTUAL)) {
-                std::shared_ptr<KeyCharacterMap> keyboardLayout =
-                        mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
-                bool shouldBumpGeneration = false;
-                for_each_subdevice(
-                        [&keyboardLayout, &shouldBumpGeneration](InputDeviceContext& context) {
-                            if (context.setKeyboardLayoutOverlay(keyboardLayout)) {
-                                shouldBumpGeneration = true;
-                            }
-                        });
-                if (shouldBumpGeneration) {
-                    bumpGeneration();
-                }
-            }
+            mIsWaking = mConfiguration.getBool("device.wake").value_or(false);
         }
 
         if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) {
@@ -249,15 +263,6 @@
             }
         }
 
-        if (changes.test(Change::ENABLED_STATE)) {
-            // Do not execute this code on the first configure, because 'setEnabled' would call
-            // InputMapper::reset, and you can't reset a mapper before it has been configured.
-            // The mappers are configured for the first time at the bottom of this function.
-            auto it = readerConfig.disabledDevices.find(mId);
-            bool enabled = it == readerConfig.disabledDevices.end();
-            out += setEnabled(enabled, when);
-        }
-
         if (!changes.any() || changes.test(Change::DISPLAY_INFO)) {
             // In most situations, no port or name will be specified.
             mAssociatedDisplayPort = std::nullopt;
@@ -281,12 +286,8 @@
                 }
             }
 
-            // If the device was explicitly disabled by the user, it would be present in the
-            // "disabledDevices" list. If it is associated with a specific display, and it was not
-            // explicitly disabled, then enable/disable the device based on whether we can find the
-            // corresponding viewport.
-            bool enabled =
-                    (readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end());
+            // If it is associated with a specific display, then find the corresponding viewport
+            // which will be used to enable/disable the device.
             if (mAssociatedDisplayPort) {
                 mAssociatedViewport =
                         readerConfig.getDisplayViewportByPort(*mAssociatedDisplayPort);
@@ -294,7 +295,6 @@
                     ALOGW("Input device %s should be associated with display on port %" PRIu8 ", "
                           "but the corresponding viewport is not found.",
                           getName().c_str(), *mAssociatedDisplayPort);
-                    enabled = false;
                 }
             } else if (mAssociatedDisplayUniqueId != std::nullopt) {
                 mAssociatedViewport =
@@ -303,16 +303,8 @@
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
                           getName().c_str(), mAssociatedDisplayUniqueId->c_str());
-                    enabled = false;
                 }
             }
-
-            if (changes.any()) {
-                // For first-time configuration, only allow device to be disabled after mappers have
-                // finished configuring. This is because we need to read some of the properties from
-                // the device's open fd.
-                out += setEnabled(enabled, when);
-            }
         }
 
         for_each_mapper([this, when, &readerConfig, changes, &out](InputMapper& mapper) {
@@ -320,12 +312,11 @@
             mSources |= mapper.getSources();
         });
 
-        // If a device is just plugged but it might be disabled, we need to update some info like
-        // axis range of touch from each InputMapper first, then disable it.
-        if (!changes.any()) {
-            out += setEnabled(readerConfig.disabledDevices.find(mId) ==
-                                      readerConfig.disabledDevices.end(),
-                              when);
+        if (!changes.any() || changes.test(Change::ENABLED_STATE) ||
+            changes.test(Change::DISPLAY_INFO)) {
+            // Whether a device is enabled can depend on the display association,
+            // so update the enabled state when there is a change in display info.
+            out += updateEnableState(when, readerConfig, forceEnable);
         }
     }
     return out;
@@ -376,9 +367,25 @@
         }
         --count;
     }
+    postProcess(out);
     return out;
 }
 
+void InputDevice::postProcess(std::list<NotifyArgs>& args) const {
+    if (mIsWaking) {
+        // Update policy flags to request wake for the `NotifyArgs` that come from waking devices.
+        for (auto& arg : args) {
+            if (const auto notifyMotionArgs = std::get_if<NotifyMotionArgs>(&arg)) {
+                notifyMotionArgs->policyFlags |= POLICY_FLAG_WAKE;
+            } else if (const auto notifySwitchArgs = std::get_if<NotifySwitchArgs>(&arg)) {
+                notifySwitchArgs->policyFlags |= POLICY_FLAG_WAKE;
+            } else if (const auto notifyKeyArgs = std::get_if<NotifyKeyArgs>(&arg)) {
+                notifyKeyArgs->policyFlags |= POLICY_FLAG_WAKE;
+            }
+        }
+    }
+}
+
 std::list<NotifyArgs> InputDevice::timeoutExpired(nsecs_t when) {
     std::list<NotifyArgs> out;
     for_each_mapper([&](InputMapper& mapper) { out += mapper.timeoutExpired(when); });
@@ -503,9 +510,9 @@
         classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
         mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
-        mappers.push_back(std::make_unique<MultiTouchInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH)) {
-        mappers.push_back(std::make_unique<SingleTouchInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<SingleTouchInputMapper>(contextPtr, readerConfig));
     }
 
     // Joystick-like devices.
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 7f63355..5766b14 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -236,7 +236,7 @@
     }
 
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
-    std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
+    std::shared_ptr<InputDevice> device = createDeviceLocked(when, eventHubId, identifier);
 
     mPendingArgs += device->configure(when, mConfig, /*changes=*/{});
     mPendingArgs += device->reset(when);
@@ -319,7 +319,7 @@
 }
 
 std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
-        int32_t eventHubId, const InputDeviceIdentifier& identifier) {
+        nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier) {
     auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
         const InputDeviceIdentifier identifier2 =
                 devicePair.second->getDeviceInfo().getIdentifier();
@@ -334,7 +334,7 @@
         device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
                                                identifier);
     }
-    device->addEventHubDevice(eventHubId, mConfig);
+    mPendingArgs += device->addEventHubDevice(when, eventHubId, mConfig);
     return device;
 }
 
@@ -946,6 +946,7 @@
         device->dump(dump, eventHubDevStr);
     }
 
+    dump += StringPrintf(INDENT "NextTimeout: %" PRId64 "\n", mNextTimeout);
     dump += INDENT "Configuration:\n";
     dump += INDENT2 "ExcludedDeviceNames: [";
     for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 024187f..0bcab42 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -19,6 +19,8 @@
 #include <bitset>
 #include <climits>
 #include <filesystem>
+#include <functional>
+#include <map>
 #include <ostream>
 #include <string>
 #include <unordered_map>
@@ -411,7 +413,17 @@
      * Note the parameter "bit" is an index to the bit, 0 <= bit < BITS.
      */
     inline bool test(size_t bit) const {
-        return (bit < BITS) ? mData[bit / WIDTH].test(bit % WIDTH) : false;
+        return (bit < BITS) && mData[bit / WIDTH].test(bit % WIDTH);
+    }
+    /* Sets the given bit in the bit array to given value.
+     * Returns true if the given bit is a valid index and thus was set successfully.
+     */
+    inline bool set(size_t bit, bool value) {
+        if (bit >= BITS) {
+            return false;
+        }
+        mData[bit / WIDTH].set(bit % WIDTH, value);
+        return true;
     }
     /* Returns total number of bytes needed for the array */
     inline size_t bytes() { return (BITS + CHAR_BIT - 1) / CHAR_BIT; }
@@ -459,6 +471,20 @@
             mData[i] = std::bitset<WIDTH>(buffer[i]);
         }
     }
+    /* Dump the indices in the bit array that are set. */
+    inline std::string dumpSetIndices(std::string separator,
+                                      std::function<std::string(size_t /*index*/)> format) {
+        std::string dmp;
+        for (size_t i = 0; i < BITS; i++) {
+            if (test(i)) {
+                if (!dmp.empty()) {
+                    dmp += separator;
+                }
+                dmp += format(i);
+            }
+        }
+        return dmp.empty() ? "<none>" : dmp;
+    }
 
 private:
     std::array<std::bitset<WIDTH>, COUNT> mData;
@@ -600,22 +626,26 @@
 
         ftl::Flags<InputDeviceClass> classes;
 
-        BitArray<KEY_MAX> keyBitmask;
-        BitArray<KEY_MAX> keyState;
-        BitArray<ABS_MAX> absBitmask;
-        BitArray<REL_MAX> relBitmask;
-        BitArray<SW_MAX> swBitmask;
-        BitArray<SW_MAX> swState;
-        BitArray<LED_MAX> ledBitmask;
-        BitArray<FF_MAX> ffBitmask;
-        BitArray<INPUT_PROP_MAX> propBitmask;
-        BitArray<MSC_MAX> mscBitmask;
+        BitArray<KEY_CNT> keyBitmask;
+        BitArray<KEY_CNT> keyState;
+        BitArray<REL_CNT> relBitmask;
+        BitArray<SW_CNT> swBitmask;
+        BitArray<SW_CNT> swState;
+        BitArray<LED_CNT> ledBitmask;
+        BitArray<FF_CNT> ffBitmask;
+        BitArray<INPUT_PROP_CNT> propBitmask;
+        BitArray<MSC_CNT> mscBitmask;
+        BitArray<ABS_CNT> absBitmask;
+        struct AxisState {
+            RawAbsoluteAxisInfo info;
+            int value;
+        };
+        std::map<int /*axis*/, AxisState> absState;
 
         std::string configurationFile;
         std::unique_ptr<PropertyMap> configuration;
         std::unique_ptr<VirtualKeyMap> virtualKeyMap;
         KeyMap keyMap;
-        std::unordered_map<int /*axis*/, RawAbsoluteAxisInfo> rawAbsoluteAxisInfoCache;
 
         bool ffEffectPlaying;
         int16_t ffEffectId; // initially -1
@@ -644,6 +674,7 @@
         status_t readDeviceBitMask(unsigned long ioctlCode, BitArray<N>& bitArray);
 
         void configureFd();
+        void populateAbsoluteAxisStates();
         bool hasKeycodeLocked(int keycode) const;
         void loadConfigurationLocked();
         bool loadVirtualKeyMapLocked();
@@ -653,6 +684,10 @@
         void setLedForControllerLocked();
         status_t mapLed(int32_t led, int32_t* outScanCode) const;
         void setLedStateLocked(int32_t led, bool on);
+
+        bool currentFrameDropped;
+        void trackInputEvent(const struct input_event& event);
+        void readDeviceState();
     };
 
     /**
@@ -718,13 +753,6 @@
     void addDeviceInputInotify();
     void addDeviceInotify();
 
-    /**
-     * AbsoluteAxisInfo remains unchanged for the lifetime of the device, hence
-     * we can read and store it with device
-     * @param device target device
-     */
-    static void populateDeviceAbsoluteAxisInfo(Device& device);
-
     // Protect all internal state.
     mutable std::mutex mLock;
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 2f8e5bd..31dcb2e 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -77,11 +77,11 @@
     inline bool isIgnored() { return !getMapperCount() && !mController; }
 
     bool isEnabled();
-    [[nodiscard]] std::list<NotifyArgs> setEnabled(bool enabled, nsecs_t when);
 
     void dump(std::string& dump, const std::string& eventHubDevStr);
     void addEmptyEventHubDevice(int32_t eventHubId);
-    void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig);
+    [[nodiscard]] std::list<NotifyArgs> addEventHubDevice(
+            nsecs_t when, int32_t eventHubId, const InputReaderConfiguration& readerConfig);
     void removeEventHubDevice(int32_t eventHubId);
     [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
                                                   const InputReaderConfiguration& readerConfig,
@@ -191,6 +191,7 @@
     std::unique_ptr<PeripheralControllerInterface> mController;
 
     uint32_t mSources;
+    bool mIsWaking;
     bool mIsExternal;
     std::optional<uint8_t> mAssociatedDisplayPort;
     std::optional<std::string> mAssociatedDisplayUniqueId;
@@ -205,8 +206,19 @@
     std::vector<std::unique_ptr<InputMapper>> createMappers(
             InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig);
 
+    [[nodiscard]] std::list<NotifyArgs> configureInternal(
+            nsecs_t when, const InputReaderConfiguration& readerConfig,
+            ConfigurationChanges changes, bool forceEnable = false);
+
+    [[nodiscard]] std::list<NotifyArgs> updateEnableState(
+            nsecs_t when, const InputReaderConfiguration& readerConfig, bool forceEnable = false);
+
     PropertyMap mConfiguration;
 
+    // Runs logic post a `process` call. This can be used to update the generated `NotifyArgs` as
+    // per the properties of the InputDevice.
+    void postProcess(std::list<NotifyArgs>& args) const;
+
     // helpers to interate over the devices collection
     // run a function against every mapper on every subdevice
     inline void for_each_mapper(std::function<void(InputMapper&)> f) {
@@ -284,7 +296,18 @@
         return mEventHub->getDeviceControllerNumber(mId);
     }
     inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const {
-        return mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo);
+        if (const auto status = mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); status != OK) {
+            return status;
+        }
+
+        // Validate axis info for InputDevice.
+        if (axisInfo->valid && axisInfo->minValue == axisInfo->maxValue) {
+            // Historically, we deem axes with the same min and max values as invalid to avoid
+            // dividing by zero when scaling by max - min.
+            // TODO(b/291772515): Perform axis info validation on a per-axis basis when it is used.
+            axisInfo->valid = false;
+        }
+        return OK;
     }
     inline bool hasRelativeAxis(int32_t code) const {
         return mEventHub->hasRelativeAxis(mId, code);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index e21715e..9a297c9 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -121,7 +121,7 @@
 
 protected:
     // These members are protected so they can be instrumented by test cases.
-    virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
+    virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t deviceId,
                                                             const InputDeviceIdentifier& identifier)
             REQUIRES(mLock);
 
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index c684ed4..79f07a5 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -347,7 +347,7 @@
                                                AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                                &pointerCoords, mXPrecision, mYPrecision,
                                                xCursorPosition, yCursorPosition, downTime,
-                                               /* videoFrames */ {}));
+                                               /*videoFrames=*/{}));
             }
         }
 
@@ -357,7 +357,7 @@
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                        &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                        yCursorPosition, downTime,
-                                       /* videoFrames */ {}));
+                                       /*videoFrames=*/{}));
 
         if (buttonsPressed) {
             BitSet32 pressed(buttonsPressed);
@@ -371,7 +371,7 @@
                                                AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                                &pointerCoords, mXPrecision, mYPrecision,
                                                xCursorPosition, yCursorPosition, downTime,
-                                               /* videoFrames */ {}));
+                                               /*videoFrames=*/{}));
             }
         }
 
@@ -386,7 +386,7 @@
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                            &pointerCoords, mXPrecision, mYPrecision,
                                            xCursorPosition, yCursorPosition, downTime,
-                                           /* videoFrames */ {}));
+                                           /*videoFrames=*/{}));
         }
 
         // Send scroll events.
@@ -401,7 +401,7 @@
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                            &pointerCoords, mXPrecision, mYPrecision,
                                            xCursorPosition, yCursorPosition, downTime,
-                                           /* videoFrames */ {}));
+                                           /*videoFrames=*/{}));
         }
     }
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 099a955..8a9ea75 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -352,7 +352,7 @@
                                    MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                    &pointerProperties, &pointerCoords, 0, 0,
                                    AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                   AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {}));
+                                   AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /*videoFrames=*/{}));
     return out;
 }
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 5c42e10..531fc67 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -86,20 +86,26 @@
     return ADISPLAY_ID_NONE;
 }
 
+std::optional<KeyboardLayoutInfo> KeyboardInputMapper::getKeyboardLayoutInfo() const {
+    if (mKeyboardLayoutInfo) {
+        return mKeyboardLayoutInfo;
+    }
+    std::optional<RawLayoutInfo> layoutInfo = getDeviceContext().getRawLayoutInfo();
+    if (!layoutInfo) {
+        return std::nullopt;
+    }
+    return KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType);
+}
+
 void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
     info.setKeyboardType(mKeyboardType);
     info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
 
-    if (mKeyboardLayoutInfo) {
-        info.setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
-    } else {
-        std::optional<RawLayoutInfo> layoutInfo = getDeviceContext().getRawLayoutInfo();
-        if (layoutInfo) {
-            info.setKeyboardLayoutInfo(
-                    KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType));
-        }
+    std::optional keyboardLayoutInfo = getKeyboardLayoutInfo();
+    if (keyboardLayoutInfo) {
+        info.setKeyboardLayoutInfo(*keyboardLayoutInfo);
     }
 }
 
@@ -152,13 +158,31 @@
                 getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation());
         if (mKeyboardLayoutInfo != newKeyboardLayoutInfo) {
             mKeyboardLayoutInfo = newKeyboardLayoutInfo;
+            // Also update keyboard layout overlay as soon as we find the new layout info
+            updateKeyboardLayoutOverlay();
             bumpGeneration();
         }
     }
 
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUTS)) {
+        if (!getDeviceContext().getDeviceClasses().test(InputDeviceClass::VIRTUAL) &&
+            updateKeyboardLayoutOverlay()) {
+            bumpGeneration();
+        }
+    }
     return out;
 }
 
+bool KeyboardInputMapper::updateKeyboardLayoutOverlay() {
+    std::shared_ptr<KeyCharacterMap> keyboardLayout =
+            getDeviceContext()
+                    .getContext()
+                    ->getPolicy()
+                    ->getKeyboardLayoutOverlay(getDeviceContext().getDeviceIdentifier(),
+                                               getKeyboardLayoutInfo());
+    return getDeviceContext().setKeyboardLayoutOverlay(keyboardLayout);
+}
+
 void KeyboardInputMapper::configureParameters() {
     const PropertyMap& config = getDeviceContext().getConfiguration();
     mParameters.orientationAware = config.getBool("keyboard.orientationAware").value_or(false);
@@ -205,6 +229,7 @@
     int32_t keyCode;
     int32_t keyMetaState;
     uint32_t policyFlags;
+    int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
 
     if (getDeviceContext().mapKey(scanCode, usageCode, mMetaState, &keyCode, &keyMetaState,
                                   &policyFlags)) {
@@ -226,6 +251,7 @@
             // key repeat, be sure to use same keycode as before in case of rotation
             keyCode = mKeyDowns[*keyDownIndex].keyCode;
             downTime = mKeyDowns[*keyDownIndex].downTime;
+            flags = mKeyDowns[*keyDownIndex].flags;
         } else {
             // key down
             if ((policyFlags & POLICY_FLAG_VIRTUAL) &&
@@ -234,12 +260,14 @@
             }
             if (policyFlags & POLICY_FLAG_GESTURE) {
                 out += getDeviceContext().cancelTouch(when, readTime);
+                flags |= AKEY_EVENT_FLAG_KEEP_TOUCH_MODE;
             }
 
             KeyDown keyDown;
             keyDown.keyCode = keyCode;
             keyDown.scanCode = scanCode;
             keyDown.downTime = when;
+            keyDown.flags = flags;
             mKeyDowns.push_back(keyDown);
         }
         onKeyDownProcessed();
@@ -249,6 +277,7 @@
             // key up, be sure to use same keycode as before in case of rotation
             keyCode = mKeyDowns[*keyDownIndex].keyCode;
             downTime = mKeyDowns[*keyDownIndex].downTime;
+            flags = mKeyDowns[*keyDownIndex].flags;
             mKeyDowns.erase(mKeyDowns.begin() + *keyDownIndex);
         } else {
             // key was not actually down
@@ -282,9 +311,8 @@
 
     out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
                                    mSource, getDisplayId(), policyFlags,
-                                   down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
-                                   AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState,
-                                   downTime));
+                                   down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags,
+                                   keyCode, scanCode, keyMetaState, downTime));
     return out;
 }
 
@@ -411,7 +439,7 @@
         out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when,
                                        systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
                                        getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP,
-                                       AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED,
+                                       mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED,
                                        mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
                                        mKeyDowns[i].downTime));
     }
@@ -423,7 +451,8 @@
 void KeyboardInputMapper::onKeyDownProcessed() {
     InputReaderContext& context = *getContext();
     if (context.isPreventingTouchpadTaps()) {
-        // avoid pinging java service unnecessarily
+        // avoid pinging java service unnecessarily, just fade pointer again if it became visible
+        context.fadePointer();
         return;
     }
     // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 96044eb..09808df 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -57,6 +57,7 @@
         nsecs_t downTime{};
         int32_t keyCode{};
         int32_t scanCode{};
+        int32_t flags{};
     };
 
     uint32_t mSource{};
@@ -98,6 +99,8 @@
     bool updateMetaStateIfNeeded(int32_t keyCode, bool down);
 
     std::optional<size_t> findKeyDownIndex(int32_t scanCode);
+    std::optional<KeyboardLayoutInfo> getKeyboardLayoutInfo() const;
+    bool updateKeyboardLayoutOverlay();
 
     void resetLedState();
     void initializeLedState(LedState& ledState, int32_t led);
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index f300ee1..1d788df 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -27,8 +27,6 @@
     friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
                                                 const InputReaderConfiguration& readerConfig,
                                                 Args... args);
-    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
-                                   const InputReaderConfiguration& readerConfig);
 
     ~MultiTouchInputMapper() override;
 
@@ -41,6 +39,8 @@
     bool hasStylus() const override;
 
 private:
+    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
+                                   const InputReaderConfiguration& readerConfig);
     // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
     // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
     // It is used to simulate stylus events for debugging and testing on a device that does not
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 13f2e59..07ae5b1 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -20,6 +20,7 @@
 
 #include "RotaryEncoderInputMapper.h"
 
+#include <utils/Timers.h>
 #include <optional>
 
 #include "CursorScrollAccumulator.h"
@@ -62,6 +63,7 @@
     dump += INDENT2 "Rotary Encoder Input Mapper:\n";
     dump += StringPrintf(INDENT3 "HaveWheel: %s\n",
                          toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel()));
+    dump += StringPrintf(INDENT3 "HaveSlopController: %s\n", toString(mSlopController != nullptr));
 }
 
 std::list<NotifyArgs> RotaryEncoderInputMapper::reconfigure(nsecs_t when,
@@ -70,6 +72,16 @@
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
     if (!changes.any()) {
         mRotaryEncoderScrollAccumulator.configure(getDeviceContext());
+
+        const PropertyMap& propertyMap = getDeviceContext().getConfiguration();
+        float slopThreshold = propertyMap.getInt("rotary_encoder.slop_threshold").value_or(0);
+        int32_t slopDurationNs = milliseconds_to_nanoseconds(
+                propertyMap.getInt("rotary_encoder.slop_duration_ms").value_or(0));
+        if (slopThreshold > 0 && slopDurationNs > 0) {
+            mSlopController = std::make_unique<SlopController>(slopThreshold, slopDurationNs);
+        } else {
+            mSlopController = nullptr;
+        }
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
         std::optional<DisplayViewport> internalViewport =
@@ -103,6 +115,10 @@
     std::list<NotifyArgs> out;
 
     float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
+    if (mSlopController) {
+        scroll = mSlopController->consumeEvent(when, scroll);
+    }
+
     bool scrolled = scroll != 0;
 
     // Send motion event.
@@ -132,10 +148,10 @@
         out.push_back(
                 NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                                  displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
-                                 metaState, /* buttonState */ 0, MotionClassification::NONE,
+                                 metaState, /*buttonState=*/0, MotionClassification::NONE,
                                  AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                  &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {}));
+                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /*videoFrames=*/{}));
     }
 
     mRotaryEncoderScrollAccumulator.finishSync();
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 9e2e8c4..fe5d152 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -20,6 +20,7 @@
 
 #include "CursorScrollAccumulator.h"
 #include "InputMapper.h"
+#include "SlopController.h"
 
 namespace android {
 
@@ -46,6 +47,7 @@
     int32_t mSource;
     float mScalingFactor;
     ui::Rotation mOrientation;
+    std::unique_ptr<SlopController> mSlopController;
 
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
                                       const InputReaderConfiguration& readerConfig);
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
index dac53cf..7726bfb 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -27,8 +27,6 @@
     friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
                                                 const InputReaderConfiguration& readerConfig,
                                                 Args... args);
-    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
-                                    const InputReaderConfiguration& readerConfig);
 
     ~SingleTouchInputMapper() override;
 
@@ -42,6 +40,8 @@
 
 private:
     SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
+                                    const InputReaderConfiguration& readerConfig);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/SlopController.cpp b/services/inputflinger/reader/mapper/SlopController.cpp
new file mode 100644
index 0000000..f79219f
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SlopController.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// clang-format off
+#include "../Macros.h"
+// clang-format on
+
+#include "SlopController.h"
+
+namespace {
+int signOf(float value) {
+    if (value == 0) return 0;
+    if (value > 0) return 1;
+    return -1;
+}
+} // namespace
+
+namespace android {
+
+SlopController::SlopController(float slopThreshold, nsecs_t slopDurationNanos)
+      : mSlopThreshold(slopThreshold), mSlopDurationNanos(slopDurationNanos) {}
+
+float SlopController::consumeEvent(nsecs_t eventTimeNanos, float value) {
+    if (mSlopDurationNanos == 0) {
+        return value;
+    }
+
+    if (shouldResetSlopTracking(eventTimeNanos, value)) {
+        mCumulativeValue = 0;
+        mHasSlopBeenMet = false;
+    }
+
+    mLastEventTimeNanos = eventTimeNanos;
+
+    if (mHasSlopBeenMet) {
+        // Since slop has already been met, we know that all of the current value would pass the
+        // slop threshold. So return that, without any further processing.
+        return value;
+    }
+
+    mCumulativeValue += value;
+
+    if (abs(mCumulativeValue) >= mSlopThreshold) {
+        mHasSlopBeenMet = true;
+        // Return the amount of value that exceeds the slop.
+        return signOf(value) * (abs(mCumulativeValue) - mSlopThreshold);
+    }
+
+    return 0;
+}
+
+bool SlopController::shouldResetSlopTracking(nsecs_t eventTimeNanos, float value) const {
+    const nsecs_t ageNanos = eventTimeNanos - mLastEventTimeNanos;
+    if (ageNanos >= mSlopDurationNanos) {
+        return true;
+    }
+    if (value == 0) {
+        return false;
+    }
+    if (signOf(mCumulativeValue) != signOf(value)) {
+        return true;
+    }
+    return false;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/SlopController.h b/services/inputflinger/reader/mapper/SlopController.h
new file mode 100644
index 0000000..c106410
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SlopController.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/Timers.h>
+
+namespace android {
+
+/**
+ * Controls a slop logic. Slop here refers to an approach to try and drop insignificant input
+ * events. This is helpful in cases where unintentional input events may cause unintended outcomes,
+ * like scrolling a screen or keeping the screen awake.
+ *
+ * Current slop logic:
+ *      "If time since last event > Xns, then discard the next N values."
+ */
+class SlopController final {
+public:
+    SlopController(float slopThreshold, nsecs_t slopDurationNanos);
+
+    /**
+     * Consumes an event with a given time and value for slop processing.
+     * Returns an amount <=value that should be consumed.
+     */
+    float consumeEvent(nsecs_t eventTime, float value);
+
+private:
+    bool shouldResetSlopTracking(nsecs_t eventTimeNanos, float value) const;
+
+    /** The amount of event values ignored after an inactivity of the slop duration. */
+    const float mSlopThreshold;
+    /** The duration of inactivity that resets slop controlling. */
+    const nsecs_t mSlopDurationNanos;
+
+    nsecs_t mLastEventTimeNanos = 0;
+    float mCumulativeValue = 0;
+    bool mHasSlopBeenMet = false;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 587e845..3c26d1d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -274,7 +274,7 @@
                          toString(mFusedStylusPointerId).c_str());
     dump += StringPrintf(INDENT4 "External Stylus Data Timeout: %" PRId64 "\n",
                          mExternalStylusFusionTimeout);
-    dump += StringPrintf(INDENT4 " External Stylus Buttons Applied: 0x%08x",
+    dump += StringPrintf(INDENT4 "External Stylus Buttons Applied: 0x%08x\n",
                          mExternalStylusButtonsApplied);
     dump += INDENT3 "External Stylus State:\n";
     dumpStylusState(dump, mExternalStylusState);
@@ -972,7 +972,18 @@
             (rawXResolution > 0 && rawYResolution > 0) ? (rawXResolution + rawYResolution) / 2 : 0;
 
     const DisplayViewport& newViewport = newViewportOpt.value_or(kUninitializedViewport);
-    const bool viewportChanged = mViewport != newViewport;
+    bool viewportChanged;
+    if (mParameters.enableForInactiveViewport) {
+        // When touch is enabled for an inactive viewport, ignore the
+        // viewport active status when checking whether the viewport has
+        // changed.
+        DisplayViewport tempViewport = mViewport;
+        tempViewport.isActive = newViewport.isActive;
+        viewportChanged = tempViewport != newViewport;
+    } else {
+        viewportChanged = mViewport != newViewport;
+    }
+
     bool skipViewportUpdate = false;
     if (viewportChanged) {
         const bool viewportOrientationChanged = mViewport.orientation != newViewport.orientation;
@@ -2008,12 +2019,12 @@
         PointerCoords& curOutCoords = outCoords[outIndex];
 
         if (curInProperties != curOutProperties) {
-            curOutProperties.copyFrom(curInProperties);
+            curOutProperties = curInProperties;
             changed = true;
         }
 
         if (curInCoords != curOutCoords) {
-            curOutCoords.copyFrom(curInCoords);
+            curOutCoords = curInCoords;
             changed = true;
         }
     }
@@ -2743,7 +2754,7 @@
                                        buttonState, MotionClassification::NONE,
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                        &pointerCoords, 0, 0, x, y, mPointerGesture.downTime,
-                                       /* videoFrames */ {}));
+                                       /*videoFrames=*/{}));
     }
 
     // Update state.
@@ -2755,10 +2766,9 @@
         for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty();) {
             uint32_t id = idBits.clearFirstMarkedBit();
             uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
-            mPointerGesture.lastGestureProperties[index].copyFrom(
-                    mPointerGesture.currentGestureProperties[index]);
-            mPointerGesture.lastGestureCoords[index].copyFrom(
-                    mPointerGesture.currentGestureCoords[index]);
+            mPointerGesture.lastGestureProperties[index] =
+                    mPointerGesture.currentGestureProperties[index];
+            mPointerGesture.lastGestureCoords[index] = mPointerGesture.currentGestureCoords[index];
             mPointerGesture.lastGestureIdToIndex[id] = index;
         }
     }
@@ -3542,8 +3552,7 @@
             std::tie(x, y) = mPointerController->getPosition();
         }
 
-        mPointerSimple.currentCoords.copyFrom(
-                mCurrentCookedState.cookedPointerData.pointerCoords[index]);
+        mPointerSimple.currentCoords = mCurrentCookedState.cookedPointerData.pointerCoords[index];
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerSimple.currentProperties.id = 0;
@@ -3581,8 +3590,8 @@
 
         const auto [x, y] = mPointerController->getPosition();
         const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
-        mPointerSimple.currentCoords.copyFrom(
-                mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]);
+        mPointerSimple.currentCoords =
+                mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex];
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
@@ -3642,7 +3651,7 @@
                                        mOrientedXPrecision, mOrientedYPrecision,
                                        mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
                                        mPointerSimple.downTime,
-                                       /* videoFrames */ {}));
+                                       /*videoFrames=*/{}));
     }
 
     if (mPointerSimple.hovering && !hovering) {
@@ -3657,7 +3666,7 @@
                                  &mPointerSimple.lastCoords, mOrientedXPrecision,
                                  mOrientedYPrecision, mPointerSimple.lastCursorX,
                                  mPointerSimple.lastCursorY, mPointerSimple.downTime,
-                                 /* videoFrames */ {}));
+                                 /*videoFrames=*/{}));
     }
 
     if (down) {
@@ -3674,7 +3683,7 @@
                                            &mPointerSimple.currentProperties,
                                            &mPointerSimple.currentCoords, mOrientedXPrecision,
                                            mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
-                                           mPointerSimple.downTime, /* videoFrames */ {}));
+                                           mPointerSimple.downTime, /*videoFrames=*/{}));
         }
 
         // Send move.
@@ -3685,7 +3694,7 @@
                                        &mPointerSimple.currentProperties,
                                        &mPointerSimple.currentCoords, mOrientedXPrecision,
                                        mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
-                                       mPointerSimple.downTime, /* videoFrames */ {}));
+                                       mPointerSimple.downTime, /*videoFrames=*/{}));
     }
 
     if (hovering) {
@@ -3701,7 +3710,7 @@
                                            &mPointerSimple.currentProperties,
                                            &mPointerSimple.currentCoords, mOrientedXPrecision,
                                            mOrientedYPrecision, cursorPosition.x, cursorPosition.y,
-                                           mPointerSimple.downTime, /* videoFrames */ {}));
+                                           mPointerSimple.downTime, /*videoFrames=*/{}));
         }
 
         // Send hover move.
@@ -3712,7 +3721,7 @@
                                  MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                                  &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
                                  mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x,
-                                 cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {}));
+                                 cursorPosition.y, mPointerSimple.downTime, /*videoFrames=*/{}));
     }
 
     if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) {
@@ -3722,8 +3731,7 @@
         mWheelXVelocityControl.move(when, &hscroll, nullptr);
 
         // Send scroll.
-        PointerCoords pointerCoords;
-        pointerCoords.copyFrom(mPointerSimple.currentCoords);
+        PointerCoords pointerCoords = mPointerSimple.currentCoords;
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
 
@@ -3734,13 +3742,13 @@
                                        &mPointerSimple.currentProperties, &pointerCoords,
                                        mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x,
                                        cursorPosition.y, mPointerSimple.downTime,
-                                       /* videoFrames */ {}));
+                                       /*videoFrames=*/{}));
     }
 
     // Save state.
     if (down || hovering) {
-        mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
-        mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+        mPointerSimple.lastCoords = mPointerSimple.currentCoords;
+        mPointerSimple.lastProperties = mPointerSimple.currentProperties;
         mPointerSimple.displayId = displayId;
         mPointerSimple.source = mSource;
         mPointerSimple.lastCursorX = cursorPosition.x;
@@ -3765,7 +3773,7 @@
                                        mOrientedXPrecision, mOrientedYPrecision,
                                        mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
                                        mPointerSimple.downTime,
-                                       /* videoFrames */ {}));
+                                       /*videoFrames=*/{}));
         if (mPointerController != nullptr) {
             mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
         }
@@ -3774,34 +3782,26 @@
     return out;
 }
 
-static bool isStylusEvent(uint32_t source, int32_t action, const PointerProperties* properties) {
-    if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
-        return false;
-    }
-    const auto actionIndex = action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-    return isStylusToolType(properties[actionIndex].toolType);
-}
-
 NotifyMotionArgs TouchInputMapper::dispatchMotion(
         nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
         int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
         int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
         const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
         float yPrecision, nsecs_t downTime, MotionClassification classification) {
-    PointerCoords pointerCoords[MAX_POINTERS];
-    PointerProperties pointerProperties[MAX_POINTERS];
+    std::vector<PointerCoords> pointerCoords;
+    std::vector<PointerProperties> pointerProperties;
     uint32_t pointerCount = 0;
     while (!idBits.isEmpty()) {
         uint32_t id = idBits.clearFirstMarkedBit();
         uint32_t index = idToIndex[id];
-        pointerProperties[pointerCount].copyFrom(properties[index]);
-        pointerCoords[pointerCount].copyFrom(coords[index]);
+        pointerProperties.push_back(properties[index]);
+        pointerCoords.push_back(coords[index]);
 
         if (changedId >= 0 && id == uint32_t(changedId)) {
             action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
         }
 
-        pointerCount += 1;
+        pointerCount++;
     }
 
     ALOG_ASSERT(pointerCount != 0);
@@ -3829,7 +3829,7 @@
 
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
-            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties) &&
+            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, pointerProperties) &&
             mPointerController && displayId != ADISPLAY_ID_NONE &&
             displayId == mPointerController->getDisplayId();
     if (showDirectStylusPointer) {
@@ -3861,9 +3861,9 @@
                   [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); });
     return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
                             policyFlags, action, actionButton, flags, metaState, buttonState,
-                            classification, edgeFlags, pointerCount, pointerProperties,
-                            pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
-                            downTime, std::move(frames));
+                            classification, edgeFlags, pointerCount, pointerProperties.data(),
+                            pointerCoords.data(), xPrecision, yPrecision, xCursorPosition,
+                            yCursorPosition, downTime, std::move(frames));
 }
 
 std::list<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 97f41cc..bd9371d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -195,9 +195,9 @@
     enum class DeviceMode {
         DISABLED,   // input is disabled
         DIRECT,     // direct mapping (touchscreen)
-        UNSCALED,   // unscaled mapping (touchpad)
+        UNSCALED,   // unscaled mapping (e.g. captured touchpad)
         NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
-        POINTER,    // pointer mapping (pointer)
+        POINTER,    // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
 
         ftl_last = POINTER
     };
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 3e092d3..c76fec7 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -25,6 +25,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 <linux/input-event-codes.h>
@@ -33,8 +34,12 @@
 #include <statslog.h>
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
+#include "gestures/HardwareProperties.h"
+#include "gestures/TimerProvider.h"
 #include "ui/Rotation.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
@@ -119,57 +124,6 @@
     return output;
 }
 
-short getMaxTouchCount(const InputDeviceContext& context) {
-    if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5;
-    if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4;
-    if (context.hasScanCode(BTN_TOOL_TRIPLETAP)) return 3;
-    if (context.hasScanCode(BTN_TOOL_DOUBLETAP)) return 2;
-    if (context.hasScanCode(BTN_TOOL_FINGER)) return 1;
-    return 0;
-}
-
-HardwareProperties createHardwareProperties(const InputDeviceContext& context) {
-    HardwareProperties props;
-    RawAbsoluteAxisInfo absMtPositionX;
-    context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX);
-    props.left = absMtPositionX.minValue;
-    props.right = absMtPositionX.maxValue;
-    props.res_x = absMtPositionX.resolution;
-
-    RawAbsoluteAxisInfo absMtPositionY;
-    context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY);
-    props.top = absMtPositionY.minValue;
-    props.bottom = absMtPositionY.maxValue;
-    props.res_y = absMtPositionY.resolution;
-
-    RawAbsoluteAxisInfo absMtOrientation;
-    context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation);
-    props.orientation_minimum = absMtOrientation.minValue;
-    props.orientation_maximum = absMtOrientation.maxValue;
-
-    RawAbsoluteAxisInfo absMtSlot;
-    context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot);
-    props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1;
-    props.max_touch_cnt = getMaxTouchCount(context);
-
-    // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5
-    // fingers but only report the coordinates of 2 of them. We don't know of any external touchpads
-    // that did this, so assume false.
-    props.supports_t5r2 = false;
-
-    props.support_semi_mt = context.hasInputProperty(INPUT_PROP_SEMI_MT);
-    props.is_button_pad = context.hasInputProperty(INPUT_PROP_BUTTONPAD);
-
-    // Mouse-only properties, which will always be false.
-    props.has_wheel = false;
-    props.wheel_is_hi_res = false;
-
-    // Linux Kernel haptic touchpad support isn't merged yet, so for now assume that no touchpads
-    // are haptic.
-    props.is_haptic_pad = false;
-    return props;
-}
-
 void gestureInterpreterCallback(void* clientData, const Gesture* gesture) {
     TouchpadInputMapper* mapper = static_cast<TouchpadInputMapper*>(clientData);
     mapper->consumeGesture(gesture);
@@ -288,6 +242,7 @@
       : InputMapper(deviceContext, readerConfig),
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
         mPointerController(getContext()->getPointerController(getDeviceId())),
+        mTimerProvider(*getContext()),
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
@@ -309,8 +264,12 @@
     // 2) TouchpadInputMapper is stored as a unique_ptr and not moved.
     mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider),
                                          &mPropertyProvider);
+    if (input_flags::enable_gestures_library_timer_provider()) {
+        mGestureInterpreter->SetTimerProvider(const_cast<GesturesTimerProvider*>(
+                                                      &kGestureTimerProvider),
+                                              &mTimerProvider);
+    }
     mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
-    // TODO(b/251196347): set a timer provider, so the library can use timers.
 }
 
 TouchpadInputMapper::~TouchpadInputMapper() {
@@ -318,14 +277,14 @@
         mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
     }
 
-    // The gesture interpreter's destructor will call its property provider's free function for all
-    // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer
-    // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may
-    // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on
-    // declaration order to avoid crashes seems rather fragile, so explicitly clear the property
-    // provider here to ensure all the freeProperty calls happen before mPropertyProvider is
-    // destructed.
+    // The gesture interpreter's destructor will try to free its property and timer providers,
+    // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers.
+    // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already
+    // been freed, causing allocation errors or use-after-free bugs. Depending on declaration order
+    // to avoid this seems rather fragile, so explicitly clear the providers here to ensure all the
+    // freeProperty and freeTimer calls happen before the providers are destructed.
     mGestureInterpreter->SetPropProvider(nullptr, nullptr);
+    mGestureInterpreter->SetTimerProvider(nullptr, nullptr);
 }
 
 uint32_t TouchpadInputMapper::getSources() const {
@@ -343,9 +302,6 @@
 
 void TouchpadInputMapper::dump(std::string& dump) {
     dump += INDENT2 "Touchpad Input Mapper:\n";
-    if (mProcessing) {
-        dump += INDENT3 "Currently processing a hardware state\n";
-    }
     if (mResettingInterpreter) {
         dump += INDENT3 "Currently resetting gesture interpreter\n";
     }
@@ -354,8 +310,15 @@
     dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
     dump += INDENT3 "Gesture properties:\n";
     dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
+    if (input_flags::enable_gestures_library_timer_provider()) {
+        dump += INDENT3 "Timer provider:\n";
+        dump += addLinePrefix(mTimerProvider.dump(), INDENT4);
+    } else {
+        dump += INDENT3 "Timer provider: disabled by flag\n";
+    }
     dump += INDENT3 "Captured event converter:\n";
     dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
+    dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when,
@@ -367,13 +330,31 @@
     }
 
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
-        std::optional<int32_t> displayId = mPointerController->getDisplayId();
+        mDisplayId = ADISPLAY_ID_NONE;
+        if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+            // This InputDevice is associated with a viewport.
+            // Only generate events for the associated display.
+            const bool mismatchedPointerDisplay =
+                    (viewport->displayId != mPointerController->getDisplayId());
+            if (mismatchedPointerDisplay) {
+                ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
+                      "controller",
+                      mDeviceContext.getName().c_str());
+            }
+            mDisplayId = mismatchedPointerDisplay ? std::nullopt
+                                                  : std::make_optional(viewport->displayId);
+        } else {
+            // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
+            mDisplayId = mPointerController->getDisplayId();
+        }
+
         ui::Rotation orientation = ui::ROTATION_0;
-        if (displayId.has_value()) {
-            if (auto viewport = config.getDisplayViewportById(*displayId); viewport) {
+        if (mDisplayId.has_value()) {
+            if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
                 orientation = getInverseRotation(viewport->orientation);
             }
         }
+        mGestureConverter.setDisplayId(mDisplayId);
         mGestureConverter.setOrientation(orientation);
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
@@ -480,13 +461,18 @@
 std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                              SelfContainedHardwareState schs) {
     ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str());
-    mProcessing = true;
     mGestureInterpreter->PushHardwareState(&schs.state);
-    mProcessing = false;
-
     return processGestures(when, readTime);
 }
 
+std::list<NotifyArgs> TouchpadInputMapper::timeoutExpired(nsecs_t when) {
+    if (!input_flags::enable_gestures_library_timer_provider()) {
+        return {};
+    }
+    mTimerProvider.triggerCallbacks(when);
+    return processGestures(when, when);
+}
+
 void TouchpadInputMapper::consumeGesture(const Gesture* gesture) {
     ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str());
     if (mResettingInterpreter) {
@@ -494,22 +480,24 @@
         // ignore any gestures produced from the interpreter while we're resetting it.
         return;
     }
-    if (!mProcessing) {
-        ALOGE("Received gesture outside of the normal processing flow; ignoring it.");
-        return;
-    }
     mGesturesToProcess.push_back(*gesture);
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out = {};
-    MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
-    for (Gesture& gesture : mGesturesToProcess) {
-        out += mGestureConverter.handleGesture(when, readTime, gesture);
-        metricsAccumulator.processGesture(mMetricsId, gesture);
+    if (mDisplayId) {
+        MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
+        for (Gesture& gesture : mGesturesToProcess) {
+            out += mGestureConverter.handleGesture(when, readTime, gesture);
+            metricsAccumulator.processGesture(mMetricsId, gesture);
+        }
     }
     mGesturesToProcess.clear();
     return out;
 }
 
+std::optional<int32_t> TouchpadInputMapper::getAssociatedDisplayId() {
+    return mDisplayId;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 73ca5af..a68ae43 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -34,6 +34,7 @@
 #include "gestures/GestureConverter.h"
 #include "gestures/HardwareStateConverter.h"
 #include "gestures/PropertyProvider.h"
+#include "gestures/TimerProvider.h"
 
 #include "include/gestures.h"
 
@@ -56,6 +57,7 @@
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
 
     void consumeGesture(const Gesture* gesture);
 
@@ -64,6 +66,8 @@
     using MetricsIdentifier = std::tuple<uint16_t /*busId*/, uint16_t /*vendorId*/,
                                          uint16_t /*productId*/, uint16_t /*version*/>;
 
+    std::optional<int32_t> getAssociatedDisplayId() override;
+
 private:
     void resetGestureInterpreter(nsecs_t when);
     explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
@@ -78,6 +82,7 @@
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
     PropertyProvider mPropertyProvider;
+    TimerProvider mTimerProvider;
 
     // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and
     // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches
@@ -90,7 +95,6 @@
     CapturedTouchpadEventConverter mCapturedEventConverter;
 
     bool mPointerCaptured = false;
-    bool mProcessing = false;
     bool mResettingInterpreter = false;
     std::vector<Gesture> mGesturesToProcess;
 
@@ -102,6 +106,11 @@
     std::set<int32_t> mLastFrameTrackingIds;
     // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
     std::set<int32_t> mPalmTrackingIds;
+
+    // The display that events generated by this mapper should target. This can be set to
+    // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+    // std::nullopt), all events will be ignored.
+    std::optional<int32_t> mDisplayId;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 3abf2bd..7006e9e 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -124,6 +124,11 @@
 
 std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
                                                       const Gesture& gesture) {
+    if (!mDisplayId) {
+        // Ignore gestures when there is no target display configured.
+        return {};
+    }
+
     switch (gesture.type) {
         case kGestureTypeMove:
             return {handleMove(when, readTime, gesture)};
@@ -556,7 +561,7 @@
             readTime,
             mDeviceId,
             SOURCE,
-            mPointerController->getDisplayId(),
+            *mDisplayId,
             /* policyFlags= */ POLICY_FLAG_WAKE,
             action,
             /* actionButton= */ actionButton,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 3ea3790..e6cf617 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -46,6 +46,8 @@
     void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
 
+    void setDisplayId(std::optional<int32_t> displayId) { mDisplayId = displayId; }
+
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
@@ -84,6 +86,7 @@
     InputReaderContext& mReaderContext;
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
+    std::optional<int32_t> mDisplayId;
     ui::Rotation mOrientation = ui::ROTATION_0;
     RawAbsoluteAxisInfo mXAxisInfo;
     RawAbsoluteAxisInfo mYAxisInfo;
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
new file mode 100644
index 0000000..04655dc
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HardwareProperties.h"
+
+namespace android {
+
+namespace {
+
+unsigned short getMaxTouchCount(const InputDeviceContext& context) {
+    if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5;
+    if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4;
+    if (context.hasScanCode(BTN_TOOL_TRIPLETAP)) return 3;
+    if (context.hasScanCode(BTN_TOOL_DOUBLETAP)) return 2;
+    if (context.hasScanCode(BTN_TOOL_FINGER)) return 1;
+    return 0;
+}
+
+} // namespace
+
+HardwareProperties createHardwareProperties(const InputDeviceContext& context) {
+    HardwareProperties props;
+    RawAbsoluteAxisInfo absMtPositionX;
+    context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX);
+    props.left = absMtPositionX.minValue;
+    props.right = absMtPositionX.maxValue;
+    props.res_x = absMtPositionX.resolution;
+
+    RawAbsoluteAxisInfo absMtPositionY;
+    context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY);
+    props.top = absMtPositionY.minValue;
+    props.bottom = absMtPositionY.maxValue;
+    props.res_y = absMtPositionY.resolution;
+
+    RawAbsoluteAxisInfo absMtOrientation;
+    context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation);
+    props.orientation_minimum = absMtOrientation.minValue;
+    props.orientation_maximum = absMtOrientation.maxValue;
+
+    RawAbsoluteAxisInfo absMtSlot;
+    context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot);
+    props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1;
+    props.max_touch_cnt = getMaxTouchCount(context);
+
+    // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5
+    // fingers but only report the coordinates of 2 of them. We don't know of any external touchpads
+    // that did this, so assume false.
+    props.supports_t5r2 = false;
+
+    props.support_semi_mt = context.hasInputProperty(INPUT_PROP_SEMI_MT);
+    props.is_button_pad = context.hasInputProperty(INPUT_PROP_BUTTONPAD);
+
+    // Mouse-only properties, which will always be false.
+    props.has_wheel = false;
+    props.wheel_is_hi_res = false;
+
+    // Linux Kernel haptic touchpad support isn't merged yet, so for now assume that no touchpads
+    // are haptic.
+    props.is_haptic_pad = false;
+
+    RawAbsoluteAxisInfo absMtPressure;
+    context.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &absMtPressure);
+    props.reports_pressure = absMtPressure.valid;
+    return props;
+}
+
+} // namespace android
diff --git a/libs/renderengine/include/renderengine/Image.h b/services/inputflinger/reader/mapper/gestures/HardwareProperties.h
similarity index 67%
copy from libs/renderengine/include/renderengine/Image.h
copy to services/inputflinger/reader/mapper/gestures/HardwareProperties.h
index 3bb4731..672f8c1 100644
--- a/libs/renderengine/include/renderengine/Image.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareProperties.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,16 +16,14 @@
 
 #pragma once
 
-struct ANativeWindowBuffer;
+#include "InputDevice.h"
+
+#include "include/gestures.h"
 
 namespace android {
-namespace renderengine {
 
-class Image {
-public:
-    virtual ~Image() = default;
-    virtual bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) = 0;
-};
+// Creates a Gestures library HardwareProperties struct for the touchpad described by the
+// InputDeviceContext.
+HardwareProperties createHardwareProperties(const InputDeviceContext& context);
 
-} // namespace renderengine
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp
new file mode 100644
index 0000000..df2f260
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TimerProvider.h"
+
+#include <chrono>
+#include <string>
+
+#include <android-base/logging.h>
+#include <input/PrintTools.h>
+
+namespace android {
+
+namespace {
+
+nsecs_t stimeToNsecs(stime_t time) {
+    return std::chrono::duration_cast<std::chrono::nanoseconds>(
+                   std::chrono::duration<stime_t>(time))
+            .count();
+}
+
+stime_t nsecsToStime(nsecs_t time) {
+    return std::chrono::duration_cast<std::chrono::duration<stime_t>>(
+                   std::chrono::nanoseconds(time))
+            .count();
+}
+
+GesturesTimer* createTimer(void* data) {
+    return static_cast<TimerProvider*>(data)->createTimer();
+}
+
+void setDeadline(void* data, GesturesTimer* timer, stime_t delay, GesturesTimerCallback callback,
+                 void* callbackData) {
+    static_cast<TimerProvider*>(data)->setDeadline(timer, stimeToNsecs(delay), callback,
+                                                   callbackData);
+};
+
+void cancelTimer(void* data, GesturesTimer* timer) {
+    static_cast<TimerProvider*>(data)->cancelTimer(timer);
+}
+
+void freeTimer(void* data, GesturesTimer* timer) {
+    static_cast<TimerProvider*>(data)->freeTimer(timer);
+}
+
+} // namespace
+
+const GesturesTimerProvider kGestureTimerProvider = {
+        .create_fn = createTimer,
+        .set_fn = setDeadline,
+        .cancel_fn = cancelTimer,
+        .free_fn = freeTimer,
+};
+
+TimerProvider::TimerProvider(InputReaderContext& context) : mReaderContext(context) {}
+
+std::string TimerProvider::dump() {
+    std::string dump;
+    auto timerPtrToString = [](const std::unique_ptr<GesturesTimer>& timer) {
+        return std::to_string(timer->id);
+    };
+    dump += "Timer IDs: " + dumpVector<std::unique_ptr<GesturesTimer>>(mTimers, timerPtrToString) +
+            "\n";
+    dump += "Deadlines and corresponding timer IDs:\n";
+    dump += addLinePrefix(dumpMap(mDeadlines, constToString,
+                                  [](const Deadline& deadline) {
+                                      return std::to_string(deadline.timerId);
+                                  }),
+                          "  ") +
+            "\n";
+    return dump;
+}
+
+void TimerProvider::triggerCallbacks(nsecs_t when) {
+    while (!mDeadlines.empty() && when >= mDeadlines.begin()->first) {
+        const auto& deadlinePair = mDeadlines.begin();
+        deadlinePair->second.callback(when);
+        mDeadlines.erase(deadlinePair);
+    }
+    requestTimeout();
+}
+
+GesturesTimer* TimerProvider::createTimer() {
+    mTimers.push_back(std::make_unique<GesturesTimer>());
+    mTimers.back()->id = mNextTimerId;
+    mNextTimerId++;
+    return mTimers.back().get();
+}
+
+void TimerProvider::setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback,
+                                void* callbackData) {
+    setDeadlineWithoutRequestingTimeout(timer, delay, callback, callbackData);
+    requestTimeout();
+}
+
+void TimerProvider::setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay,
+                                                        GesturesTimerCallback callback,
+                                                        void* callbackData) {
+    const nsecs_t now = getCurrentTime();
+    const nsecs_t time = now + delay;
+    std::function<void(nsecs_t)> wrappedCallback = [=, this](nsecs_t triggerTime) {
+        stime_t nextDelay = callback(nsecsToStime(triggerTime), callbackData);
+        if (nextDelay >= 0.0) {
+            // When rescheduling a deadline, we know that we're running inside a call to
+            // triggerCallbacks, at the end of which requestTimeout will be called. This means that
+            // we don't want to call the public setDeadline, as that will request a timeout before
+            // triggerCallbacks has removed this current deadline, resulting in a request for a
+            // timeout that has already passed.
+            setDeadlineWithoutRequestingTimeout(timer, stimeToNsecs(nextDelay), callback,
+                                                callbackData);
+        }
+    };
+    mDeadlines.insert({time, Deadline(wrappedCallback, timer->id)});
+}
+
+void TimerProvider::cancelTimer(GesturesTimer* timer) {
+    int id = timer->id;
+    std::erase_if(mDeadlines, [id](const auto& item) { return item.second.timerId == id; });
+    requestTimeout();
+}
+
+void TimerProvider::freeTimer(GesturesTimer* timer) {
+    cancelTimer(timer);
+    std::erase_if(mTimers, [timer](std::unique_ptr<GesturesTimer>& t) { return t.get() == timer; });
+}
+
+void TimerProvider::requestTimeout() {
+    if (!mDeadlines.empty()) {
+        // Because a std::multimap is sorted by key, we simply use the time for the first entry.
+        mReaderContext.requestTimeoutAtTime(mDeadlines.begin()->first);
+    }
+}
+
+nsecs_t TimerProvider::getCurrentTime() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.h b/services/inputflinger/reader/mapper/gestures/TimerProvider.h
new file mode 100644
index 0000000..7c870e0
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <utils/Timers.h>
+
+#include "InputReaderContext.h"
+#include "NotifyArgs.h"
+#include "include/gestures.h"
+
+namespace android {
+
+extern const GesturesTimerProvider kGestureTimerProvider;
+
+// Implementation of a gestures library timer provider, which allows the library to set and cancel
+// callbacks.
+class TimerProvider {
+public:
+    TimerProvider(InputReaderContext& context);
+    virtual ~TimerProvider() = default;
+
+    // Disable copy and move, since pointers to TimerProvider objects are used in callbacks.
+    TimerProvider(const TimerProvider&) = delete;
+    TimerProvider& operator=(const TimerProvider&) = delete;
+
+    std::string dump();
+    void triggerCallbacks(nsecs_t when);
+
+    // Methods to be called by the gestures library:
+    GesturesTimer* createTimer();
+    void setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback,
+                     void* callbackData);
+    void cancelTimer(GesturesTimer* timer);
+    void freeTimer(GesturesTimer* timer);
+
+protected:
+    // A wrapper for the system clock, to allow tests to override it.
+    virtual nsecs_t getCurrentTime();
+
+private:
+    void setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay,
+                                             GesturesTimerCallback callback, void* callbackData);
+    // Requests a timeout from the InputReader for the nearest deadline in mDeadlines. Must be
+    // called whenever mDeadlines is modified.
+    void requestTimeout();
+
+    InputReaderContext& mReaderContext;
+    int mNextTimerId = 0;
+    std::vector<std::unique_ptr<GesturesTimer>> mTimers;
+
+    struct Deadline {
+        Deadline(std::function<void(nsecs_t)> callback, int timerId)
+              : callback(callback), timerId(timerId) {}
+        const std::function<void(nsecs_t)> callback;
+        const int timerId;
+    };
+
+    std::multimap<nsecs_t /*time*/, Deadline> mDeadlines;
+};
+
+} // namespace android
+
+// Represents a "timer" registered by the gestures library. In practice, this just means a set of
+// deadlines that can be cancelled as a group. The library's API requires this to be in the
+// top-level namespace.
+struct GesturesTimer {
+    int id = -1;
+};
\ No newline at end of file
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
new file mode 100644
index 0000000..23c1691
--- /dev/null
+++ b/services/inputflinger/rust/Android.bp
@@ -0,0 +1,52 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generate the C++ code that Rust calls into.
+genrule {
+    name: "inputflinger_rs_bootstrap_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["inputflinger_rs_bootstrap_cxx_generated.cc"],
+}
+
+// Generate a C++ header containing the C++ bindings
+// to the Rust exported functions in lib.rs.
+genrule {
+    name: "inputflinger_rs_bootstrap_bridge_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["inputflinger_bootstrap.rs.h"],
+}
+
+rust_ffi_static {
+    name: "libinputflinger_rs",
+    crate_name: "inputflinger",
+    srcs: ["lib.rs"],
+    rustlibs: [
+        "libcxx",
+        "com.android.server.inputflinger-rust",
+        "libbinder_rs",
+        "liblog_rust",
+        "liblogger",
+    ],
+    host_supported: true,
+}
+
+cc_library_headers {
+    name: "inputflinger_rs_bootstrap_cxx_headers",
+    host_supported: true,
+    export_include_dirs: ["ffi"],
+}
diff --git a/services/inputflinger/host/main.cpp b/services/inputflinger/rust/ffi/InputFlingerBootstrap.h
similarity index 64%
rename from services/inputflinger/host/main.cpp
rename to services/inputflinger/rust/ffi/InputFlingerBootstrap.h
index 0a517cc..eb79ab5 100644
--- a/services/inputflinger/host/main.cpp
+++ b/services/inputflinger/rust/ffi/InputFlingerBootstrap.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,8 @@
  * limitations under the License.
  */
 
-#include <binder/BinderService.h>
-#include "InputFlinger.h"
+#pragma once
 
-using namespace android;
+#include <android/binder_parcel.h>
 
-int main(int, char**) {
-    ProcessState::self()->setThreadPoolMaxThreadCount(4);
-    BinderService<InputFlinger>::publishAndJoinThreadPool(true);
-    return 0;
-}
+using IInputFlingerRustBootstrapCallbackAIBinder = AIBinder;
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
new file mode 100644
index 0000000..501e435
--- /dev/null
+++ b/services/inputflinger/rust/lib.rs
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! # The rust component of InputFlinger
+//!
+//! We use cxxbridge to create IInputFlingerRust - the Rust component of inputflinger - and
+//! pass it back to C++ as a local AIDL interface.
+
+use binder::{
+    unstable_api::{AIBinder, new_spibinder,},
+    BinderFeatures, Interface, StatusCode, Strong,
+};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputFlingerRust::{
+    BnInputFlingerRust, IInputFlingerRust,
+    IInputFlingerRustBootstrapCallback::IInputFlingerRustBootstrapCallback,
+};
+use log::debug;
+
+const LOG_TAG: &str = "inputflinger_bootstrap";
+
+#[cxx::bridge]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod ffi {
+    extern "C++" {
+        include!("InputFlingerBootstrap.h");
+        type IInputFlingerRustBootstrapCallbackAIBinder;
+    }
+
+    extern "Rust" {
+        unsafe fn create_inputflinger_rust(
+            callback: *mut IInputFlingerRustBootstrapCallbackAIBinder,
+        );
+    }
+}
+
+/// Create the IInputFlingerRust implementation.
+/// This is the singular entry point from C++ into Rust.
+/// The `callback` parameter must be a valid pointer to an AIBinder implementation of
+/// the `IInputFlingerRustBootstrapCallback` interface. The IInputFlingerRust implementation that
+/// is created will be passed back through the callback from within this function.
+/// NOTE: This function must not hold a strong reference to the callback beyond its scope.
+///
+/// # Safety
+///
+/// This function is safe iff `callback` is a valid pointer to an `AIBinder` interface of type
+/// `IInputFlingerRustBootstrapCallback`. The pointer must have had its reference count manually
+/// incremented using `AIBinder_incStrong`. See `binder::unstable_api::new_spibinder`.
+unsafe fn create_inputflinger_rust(callback: *mut ffi::IInputFlingerRustBootstrapCallbackAIBinder) {
+    logger::init(
+        logger::Config::default().with_tag_on_device(LOG_TAG).with_min_level(log::Level::Trace),
+    );
+
+    let callback = callback as *mut AIBinder;
+    if callback.is_null() {
+        panic!("create_inputflinger_rust cannot be called with a null callback");
+    }
+
+    // SAFETY: Our caller guaranteed that `callback` is a valid pointer to an `AIBinder` and its
+    // reference count has been incremented..
+    let Some(callback) = (unsafe { new_spibinder(callback) }) else {
+            panic!("Failed to get SpAIBinder from raw callback pointer");
+        };
+
+    let callback: Result<Strong<dyn IInputFlingerRustBootstrapCallback>, StatusCode> =
+        callback.into_interface();
+    match callback {
+        Ok(callback) => {
+            debug!("Creating InputFlingerRust");
+            let service =
+                BnInputFlingerRust::new_binder(InputFlingerRust {}, BinderFeatures::default());
+            callback.onProvideInputFlingerRust(&service).unwrap();
+        }
+        Err(status) => {
+            panic!("Failed to convert AIBinder into the callback interface: {}", status);
+        }
+    }
+}
+
+struct InputFlingerRust {}
+
+impl Interface for InputFlingerRust {}
+
+impl IInputFlingerRust for InputFlingerRust {}
+
+impl Drop for InputFlingerRust {
+    fn drop(&mut self) {
+        debug!("Destroying InputFlingerRust");
+    }
+}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 1585fdd..64e8825 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -47,8 +47,10 @@
         "FakePointerController.cpp",
         "FocusResolver_test.cpp",
         "GestureConverter_test.cpp",
+        "HardwareProperties_test.cpp",
         "HardwareStateConverter_test.cpp",
         "InputDeviceMetricsCollector_test.cpp",
+        "InputDeviceMetricsSource_test.cpp",
         "InputMapperTest.cpp",
         "InputProcessor_test.cpp",
         "InputProcessorConverter_test.cpp",
@@ -57,9 +59,12 @@
         "InstrumentedInputReader.cpp",
         "LatencyTracker_test.cpp",
         "NotifyArgs_test.cpp",
+        "PointerChoreographer_test.cpp",
         "PreferStylusOverTouch_test.cpp",
         "PropertyProvider_test.cpp",
+        "SlopController_test.cpp",
         "SyncQueue_test.cpp",
+        "TimerProvider_test.cpp",
         "TestInputListener.cpp",
         "TouchpadInputMapper_test.cpp",
         "KeyboardInputMapper_test.cpp",
@@ -75,25 +80,9 @@
     target: {
         android: {
             shared_libs: [
-                "libinput",
                 "libvintf",
             ],
         },
-        host: {
-            sanitize: {
-                address: true,
-            },
-            include_dirs: [
-                "bionic/libc/kernel/android/uapi/",
-                "bionic/libc/kernel/uapi",
-            ],
-            cflags: [
-                "-D__ANDROID_HOST__",
-            ],
-            static_libs: [
-                "libinput",
-            ],
-        },
     },
     sanitize: {
         hwaddress: true,
@@ -104,6 +93,7 @@
         },
     },
     static_libs: [
+        "libflagtest",
         "libc++fs",
         "libgmock",
     ],
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
index 99a6a1f..b738abf 100644
--- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -29,8 +29,8 @@
 #include "FakeInputReaderPolicy.h"
 #include "InstrumentedInputReader.h"
 #include "TestConstants.h"
+#include "TestEventMatchers.h"
 #include "TestInputListener.h"
-#include "TestInputListenerMatchers.h"
 
 namespace android {
 
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 6774b17..b55c9cc 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -22,12 +22,13 @@
 #include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
-#include "TestInputListenerMatchers.h"
+#include "TestEventMatchers.h"
 
 #define TAG "CursorInputMapper_test"
 
 namespace android {
 
+using testing::AllOf;
 using testing::Return;
 using testing::VariantWith;
 constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
@@ -36,6 +37,7 @@
 constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
 
 /**
  * Unit tests for CursorInputMapper.
@@ -58,10 +60,23 @@
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
 
-        EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1));
-
         mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
+
+    void setPointerCapture(bool enabled) {
+        mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+        mReaderConfiguration.pointerCaptureRequest.seq = 1;
+        int32_t generation = mDevice->getGeneration();
+        std::list<NotifyArgs> args =
+                mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                     InputReaderConfiguration::Change::POINTER_CAPTURE);
+        ASSERT_THAT(args,
+                    ElementsAre(
+                            VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+
+        // Check that generation also got bumped
+        ASSERT_GT(mDevice->getGeneration(), generation);
+    }
 };
 
 /**
@@ -102,4 +117,83 @@
                             VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
 }
 
+/**
+ * Set pointer capture and check that ACTION_MOVE events are emitted from CursorInputMapper.
+ * During pointer capture, source should be set to MOUSE_RELATIVE. When the capture is disabled,
+ * the events should be generated normally:
+ *   1) The source should return to SOURCE_MOUSE
+ *   2) Cursor position should be incremented by the relative device movements
+ *   3) Cursor position of NotifyMotionArgs should now be getting populated.
+ * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
+ */
+TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+    setPointerCapture(true);
+    std::list<NotifyArgs> args;
+
+    // Move.
+    args += process(EV_REL, REL_X, 10);
+    args += process(EV_REL, REL_Y, 20);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(ACTION_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(10.0f, 20.0f),
+                              WithCursorPosition(INVALID_CURSOR_POSITION,
+                                                 INVALID_CURSOR_POSITION)))));
+
+    // Button press.
+    args.clear();
+    args += process(EV_KEY, BTN_MOUSE, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(ACTION_DOWN),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+
+    // Button release.
+    args.clear();
+    args += process(EV_KEY, BTN_MOUSE, 0);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_RELEASE),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(ACTION_UP),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+
+    // Another move.
+    args.clear();
+    args += process(EV_REL, REL_X, 30);
+    args += process(EV_REL, REL_Y, 40);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(ACTION_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithCoords(30.0f, 40.0f)))));
+
+    // Disable pointer capture. Afterwards, events should be generated the usual way.
+    setPointerCapture(false);
+
+    args.clear();
+    args += process(EV_REL, REL_X, 10);
+    args += process(EV_REL, REL_Y, 20);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f),
+                              WithCursorPosition(INITIAL_CURSOR_X + 10.0f,
+                                                 INITIAL_CURSOR_Y + 20.0f)))));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 30222bf..41c98ef 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -158,7 +158,8 @@
     return mConfig;
 }
 
-const std::vector<InputDeviceInfo>& FakeInputReaderPolicy::getInputDevices() const {
+const std::vector<InputDeviceInfo> FakeInputReaderPolicy::getInputDevices() const {
+    std::scoped_lock lock(mLock);
     return mInputDevices;
 }
 
@@ -228,14 +229,14 @@
 
 void FakeInputReaderPolicy::notifyInputDevicesChanged(
         const std::vector<InputDeviceInfo>& inputDevices) {
-    std::scoped_lock<std::mutex> lock(mLock);
+    std::scoped_lock lock(mLock);
     mInputDevices = inputDevices;
     mInputDevicesChanged = true;
     mDevicesChangedCondition.notify_all();
 }
 
 std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
-        const InputDeviceIdentifier&) {
+        const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
     return nullptr;
 }
 
@@ -256,7 +257,7 @@
 }
 
 void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
-    std::scoped_lock<std::mutex> lock(mLock);
+    std::scoped_lock lock(mLock);
     mStylusGestureNotified = deviceId;
 }
 
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 78bb2c3..48912a6 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -64,7 +64,7 @@
     void removeDisabledDevice(int32_t deviceId);
     void setPointerController(std::shared_ptr<FakePointerController> controller);
     const InputReaderConfiguration& getReaderConfiguration() const;
-    const std::vector<InputDeviceInfo>& getInputDevices() const;
+    const std::vector<InputDeviceInfo> getInputDevices() const;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
                                                            ui::Rotation surfaceRotation);
     void setTouchAffineTransformation(const TouchAffineTransformation t);
@@ -86,12 +86,12 @@
             int32_t /*deviceId*/) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
-            const InputDeviceIdentifier&) override;
+            const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
     std::string getDeviceAlias(const InputDeviceIdentifier&) override;
     void waitForInputDevices(std::function<void(bool)> processDevicesChanged);
     void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
 
-    std::mutex mLock;
+    mutable std::mutex mLock;
     std::condition_variable mDevicesChangedCondition;
 
     InputReaderConfiguration mConfig;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 4df0f69..d7dc800 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -19,6 +19,7 @@
 #include <EventHub.h>
 #include <gestures/GestureConverter.h>
 #include <gtest/gtest.h>
+#include <gui/constants.h>
 
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
@@ -26,8 +27,8 @@
 #include "InstrumentedInputReader.h"
 #include "NotifyArgs.h"
 #include "TestConstants.h"
+#include "TestEventMatchers.h"
 #include "TestInputListener.h"
-#include "TestInputListenerMatchers.h"
 #include "include/gestures.h"
 #include "ui/Rotation.h"
 
@@ -85,6 +86,7 @@
 TEST_F(GestureConverterTest, Move) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
@@ -93,8 +95,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 }
@@ -103,6 +105,7 @@
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
@@ -111,8 +114,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
-                      WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
 }
@@ -120,6 +123,7 @@
 TEST_F(GestureConverterTest, ButtonsChange) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     // Press left and right buttons at once
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -132,23 +136,23 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
                                       AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
                       WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
                                       AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -161,8 +165,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -174,22 +178,24 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     // Press the button
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -201,15 +207,15 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
@@ -219,8 +225,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 
@@ -234,23 +240,25 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Scroll) {
     const nsecs_t downTime = 12345;
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
@@ -261,7 +269,8 @@
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
@@ -269,7 +278,8 @@
                       WithGestureScrollDistance(0, 10, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
@@ -280,7 +290,8 @@
                       WithGestureScrollDistance(0, 5, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
@@ -292,7 +303,8 @@
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -300,6 +312,7 @@
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
@@ -309,14 +322,15 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime)));
+                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithCoords(POINTER_X - 10, POINTER_Y),
                       WithGestureScrollDistance(0, 10, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
@@ -326,7 +340,7 @@
                       WithCoords(POINTER_X - 15, POINTER_Y),
                       WithGestureScrollDistance(0, 5, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
@@ -337,12 +351,13 @@
                       WithCoords(POINTER_X - 15, POINTER_Y),
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
@@ -358,12 +373,14 @@
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
     ASSERT_EQ(1u, args.size());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+                AllOf(WithMotionClassification(MotionClassification::NONE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
@@ -387,6 +404,7 @@
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/0);
@@ -406,6 +424,7 @@
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
                          /*dy=*/5);
@@ -431,6 +450,7 @@
     // only checks movement in one dimension.
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
@@ -444,7 +464,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
                       WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -453,7 +474,8 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -462,7 +484,8 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
 
@@ -471,7 +494,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -488,7 +512,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -504,26 +529,30 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
                       WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
@@ -535,28 +564,31 @@
     NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u)));
+                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
 
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
+                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
@@ -571,7 +603,8 @@
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u)));
+                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
@@ -585,21 +618,24 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u)));
+                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                          /* dx= */ 10, /* dy= */ 0);
@@ -613,7 +649,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
                       WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger0Start = arg.pointerCoords[0];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -622,7 +659,8 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger1Start = arg.pointerCoords[1];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -631,7 +669,8 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger2Start = arg.pointerCoords[2];
     args.pop_front();
     arg = std::get<NotifyMotionArgs>(args.front());
@@ -640,7 +679,8 @@
                                        3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     PointerCoords finger3Start = arg.pointerCoords[3];
     args.pop_front();
 
@@ -649,7 +689,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
@@ -668,7 +709,8 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
@@ -686,32 +728,37 @@
                                        3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
                       WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
@@ -722,7 +769,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -730,7 +777,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
@@ -742,7 +789,7 @@
                       WithGesturePinchScaleFactor(0.8f, EPSILON),
                       WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
                       WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -753,18 +800,19 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
@@ -775,7 +823,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -783,7 +831,7 @@
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON),
                       WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
@@ -795,7 +843,7 @@
                       WithGesturePinchScaleFactor(1.2f, EPSILON),
                       WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
                       WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -806,18 +854,19 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
@@ -841,6 +890,7 @@
 TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
@@ -866,6 +916,7 @@
 TEST_F(GestureConverterTest, ResetWithButtonPressed) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
@@ -879,24 +930,25 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y),
-                      WithToolType(ToolType::FINGER)));
+                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, ResetDuringScroll) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
@@ -909,12 +961,14 @@
                       WithGestureScrollDistance(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE)));
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/10);
@@ -927,24 +981,28 @@
                                        2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER)));
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, ResetDuringPinch) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
@@ -957,18 +1015,19 @@
                                        1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                       WithMotionClassification(MotionClassification::PINCH),
                       WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER)));
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, FlingTapDown) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
@@ -977,7 +1036,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y));
     ASSERT_TRUE(mFakePointerController->isPointerShown());
@@ -987,6 +1047,7 @@
     // Tap should produce button press/release events
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -996,7 +1057,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -1007,7 +1069,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
@@ -1015,29 +1078,32 @@
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
                       WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f)));
+                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f)));
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, Click) {
     // Click should produce button press/release events
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1047,7 +1113,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
@@ -1058,7 +1125,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
@@ -1066,7 +1134,7 @@
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
                       WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f)));
+                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
@@ -1078,17 +1146,19 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f)));
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, TapWithTapToClickDisabled) {
@@ -1097,6 +1167,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1106,7 +1177,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -1127,6 +1199,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1136,7 +1209,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
@@ -1147,7 +1221,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
@@ -1155,7 +1230,7 @@
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
                       WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f)));
+                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
@@ -1167,17 +1242,19 @@
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                       WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f)));
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -1189,6 +1266,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
@@ -1197,7 +1275,8 @@
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                       WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 
diff --git a/services/inputflinger/tests/HardwareProperties_test.cpp b/services/inputflinger/tests/HardwareProperties_test.cpp
new file mode 100644
index 0000000..8dfa8c8
--- /dev/null
+++ b/services/inputflinger/tests/HardwareProperties_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gestures/HardwareProperties.h>
+
+#include <memory>
+#include <set>
+
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "InterfaceMocks.h"
+#include "TestConstants.h"
+#include "include/gestures.h"
+
+namespace android {
+
+using testing::Return;
+
+class HardwarePropertiesTest : public testing::Test {
+public:
+    HardwarePropertiesTest() {
+        EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
+        InputDeviceIdentifier identifier;
+        identifier.name = "device";
+        identifier.location = "USB1";
+        mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
+                                                /*generation=*/2, identifier);
+        mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
+    }
+
+protected:
+    static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr int32_t EVENTHUB_ID = 1;
+
+    void setupValidAxis(int axis, int32_t min, int32_t max, int32_t resolution) {
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
+                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
+                    outAxisInfo->valid = true;
+                    outAxisInfo->minValue = min;
+                    outAxisInfo->maxValue = max;
+                    outAxisInfo->flat = 0;
+                    outAxisInfo->fuzz = 0;
+                    outAxisInfo->resolution = resolution;
+                    return OK;
+                });
+    }
+
+    void setupInvalidAxis(int axis) {
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
+                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
+                    outAxisInfo->valid = false;
+                    return -1;
+                });
+    }
+
+    void setProperty(int property, bool value) {
+        EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, property))
+                .WillRepeatedly(Return(value));
+    }
+
+    void setButtonsPresent(std::set<int> buttonCodes, bool present) {
+        for (const auto& buttonCode : buttonCodes) {
+            EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, buttonCode))
+                    .WillRepeatedly(Return(present));
+        }
+    }
+
+    MockEventHubInterface mMockEventHub;
+    MockInputReaderContext mMockInputReaderContext;
+    std::unique_ptr<InputDevice> mDevice;
+    std::unique_ptr<InputDeviceContext> mDeviceContext;
+};
+
+TEST_F(HardwarePropertiesTest, FancyTouchpad) {
+    setupValidAxis(ABS_MT_POSITION_X, 0, 2048, 27);
+    setupValidAxis(ABS_MT_POSITION_Y, 0, 1500, 30);
+    setupValidAxis(ABS_MT_ORIENTATION, -3, 4, 0);
+    setupValidAxis(ABS_MT_SLOT, 0, 15, 0);
+    setupValidAxis(ABS_MT_PRESSURE, 0, 256, 0);
+
+    setProperty(INPUT_PROP_SEMI_MT, false);
+    setProperty(INPUT_PROP_BUTTONPAD, true);
+
+    setButtonsPresent({BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP,
+                       BTN_TOOL_QUINTTAP},
+                      true);
+
+    HardwareProperties hwprops = createHardwareProperties(*mDeviceContext);
+    EXPECT_NEAR(0, hwprops.left, EPSILON);
+    EXPECT_NEAR(0, hwprops.top, EPSILON);
+    EXPECT_NEAR(2048, hwprops.right, EPSILON);
+    EXPECT_NEAR(1500, hwprops.bottom, EPSILON);
+
+    EXPECT_NEAR(27, hwprops.res_x, EPSILON);
+    EXPECT_NEAR(30, hwprops.res_y, EPSILON);
+
+    EXPECT_NEAR(-3, hwprops.orientation_minimum, EPSILON);
+    EXPECT_NEAR(4, hwprops.orientation_maximum, EPSILON);
+
+    EXPECT_EQ(16, hwprops.max_finger_cnt);
+    EXPECT_EQ(5, hwprops.max_touch_cnt);
+
+    EXPECT_FALSE(hwprops.supports_t5r2);
+    EXPECT_FALSE(hwprops.support_semi_mt);
+    EXPECT_TRUE(hwprops.is_button_pad);
+    EXPECT_FALSE(hwprops.has_wheel);
+    EXPECT_FALSE(hwprops.wheel_is_hi_res);
+    EXPECT_FALSE(hwprops.is_haptic_pad);
+    EXPECT_TRUE(hwprops.reports_pressure);
+}
+
+TEST_F(HardwarePropertiesTest, BasicTouchpad) {
+    setupValidAxis(ABS_MT_POSITION_X, 0, 1024, 0);
+    setupValidAxis(ABS_MT_POSITION_Y, 0, 768, 0);
+    setupValidAxis(ABS_MT_SLOT, 0, 7, 0);
+
+    setupInvalidAxis(ABS_MT_ORIENTATION);
+    setupInvalidAxis(ABS_MT_PRESSURE);
+
+    setProperty(INPUT_PROP_SEMI_MT, false);
+    setProperty(INPUT_PROP_BUTTONPAD, false);
+
+    setButtonsPresent({BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP}, true);
+    setButtonsPresent({BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP}, false);
+
+    HardwareProperties hwprops = createHardwareProperties(*mDeviceContext);
+    EXPECT_NEAR(0, hwprops.left, EPSILON);
+    EXPECT_NEAR(0, hwprops.top, EPSILON);
+    EXPECT_NEAR(1024, hwprops.right, EPSILON);
+    EXPECT_NEAR(768, hwprops.bottom, EPSILON);
+
+    EXPECT_NEAR(0, hwprops.res_x, EPSILON);
+    EXPECT_NEAR(0, hwprops.res_y, EPSILON);
+
+    EXPECT_NEAR(0, hwprops.orientation_minimum, EPSILON);
+    EXPECT_NEAR(0, hwprops.orientation_maximum, EPSILON);
+
+    EXPECT_EQ(8, hwprops.max_finger_cnt);
+    EXPECT_EQ(3, hwprops.max_touch_cnt);
+
+    EXPECT_FALSE(hwprops.supports_t5r2);
+    EXPECT_FALSE(hwprops.support_semi_mt);
+    EXPECT_FALSE(hwprops.is_button_pad);
+    EXPECT_FALSE(hwprops.has_wheel);
+    EXPECT_FALSE(hwprops.wheel_is_hi_res);
+    EXPECT_FALSE(hwprops.is_haptic_pad);
+    EXPECT_FALSE(hwprops.reports_pressure);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 7ccfaca..85e055d 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -36,7 +36,6 @@
 
 constexpr auto USAGE_TIMEOUT = 8765309ns;
 constexpr auto TIME = 999999ns;
-constexpr auto ALL_USAGE_SOURCES = ftl::enum_range<InputDeviceUsageSource>();
 
 constexpr int32_t DEVICE_ID = 3;
 constexpr int32_t DEVICE_ID_2 = 4;
@@ -48,10 +47,6 @@
 const std::string UNIQUE_ID = "Yosemite";
 constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
 constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS;
-constexpr uint32_t KEY_SOURCES =
-        AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD;
-constexpr int32_t POINTER_1_DOWN =
-        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
 InputDeviceIdentifier generateTestIdentifier(int32_t id = DEVICE_ID) {
     InputDeviceIdentifier identifier;
@@ -66,21 +61,14 @@
 }
 
 InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID,
-                                       uint32_t sources = TOUCHSCREEN | STYLUS,
-                                       bool isAlphabetic = false) {
+                                       uint32_t sources = TOUCHSCREEN | STYLUS) {
     auto info = InputDeviceInfo();
     info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
                     "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
     info.addSource(sources);
-    info.setKeyboardType(isAlphabetic ? AINPUT_KEYBOARD_TYPE_ALPHABETIC
-                                      : AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
     return info;
 }
 
-const InputDeviceInfo ALPHABETIC_KEYBOARD_INFO =
-        generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/true);
-const InputDeviceInfo NON_ALPHABETIC_KEYBOARD_INFO =
-        generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/false);
 const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID);
 const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2);
 
@@ -94,258 +82,6 @@
 
 } // namespace
 
-// --- InputDeviceMetricsCollectorDeviceClassificationTest ---
-
-class DeviceClassificationFixture : public ::testing::Test,
-                                    public ::testing::WithParamInterface<InputDeviceUsageSource> {};
-
-TEST_P(DeviceClassificationFixture, ValidClassifications) {
-    const InputDeviceUsageSource usageSource = GetParam();
-
-    // Use a switch to ensure a test is added for all source classifications.
-    switch (usageSource) {
-        case InputDeviceUsageSource::UNKNOWN: {
-            ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
-                      getUsageSourceForKeyArgs(generateTestDeviceInfo(),
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
-                                                       .build()));
-
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::UNKNOWN};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_KEYBOARD)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::PALM)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::BUTTONS: {
-            ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::KEYBOARD: {
-            ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::DPAD: {
-            ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_DPAD_CENTER)
-                                                       .build()));
-
-            ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_DPAD_CENTER)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::GAMEPAD: {
-            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_BUTTON_A)
-                                                       .build()));
-
-            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_BUTTON_A)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::JOYSTICK: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::JOYSTICK};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_JOYSTICK)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
-                                                       .axis(AMOTION_EVENT_AXIS_GAS, 1.f))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::MOUSE: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
-                                                AINPUT_SOURCE_MOUSE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::MOUSE_CAPTURED: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE_CAPTURED};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
-                                                AINPUT_SOURCE_MOUSE_RELATIVE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
-                                                       .x(100)
-                                                       .y(200)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 100)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCHPAD: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCHPAD_CAPTURED: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD_CAPTURED};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHPAD)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 1)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 2))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::ROTARY_ENCODER: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::ROTARY_ENCODER};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
-                                                AINPUT_SOURCE_ROTARY_ENCODER)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
-                                                       .axis(AMOTION_EVENT_AXIS_SCROLL, 10)
-                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 10))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::STYLUS_DIRECT: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_DIRECT};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                STYLUS | TOUCHSCREEN)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::STYLUS_INDIRECT: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_INDIRECT};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                STYLUS | TOUCHSCREEN | AINPUT_SOURCE_MOUSE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::STYLUS_FUSED: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_FUSED};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                AINPUT_SOURCE_BLUETOOTH_STYLUS | TOUCHSCREEN)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCH_NAVIGATION: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCH_NAVIGATION};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
-                                                AINPUT_SOURCE_TOUCH_NAVIGATION)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCHSCREEN: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200))
-                                      .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER)
-                                                       .x(300)
-                                                       .y(400))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TRACKBALL: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TRACKBALL};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
-                                                AINPUT_SOURCE_TRACKBALL)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
-                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 100)
-                                                       .axis(AMOTION_EVENT_AXIS_HSCROLL, 200))
-                                      .build()));
-            break;
-        }
-    }
-}
-
-INSTANTIATE_TEST_SUITE_P(InputDeviceMetricsCollectorDeviceClassificationTest,
-                         DeviceClassificationFixture,
-                         ::testing::ValuesIn(ALL_USAGE_SOURCES.begin(), ALL_USAGE_SOURCES.end()),
-                         [](const testing::TestParamInfo<InputDeviceUsageSource>& testParamInfo) {
-                             return ftl::enum_string(testParamInfo.param);
-                         });
-
-TEST(InputDeviceMetricsCollectorDeviceClassificationTest, MixedClassificationTouchscreenStylus) {
-    std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN,
-                                          InputDeviceUsageSource::STYLUS_DIRECT};
-    ASSERT_EQ(srcs,
-              getUsageSourcesForMotionArgs(
-                      MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN | STYLUS)
-                              .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(100).y(200))
-                              .pointer(PointerBuilder(/*id=*/2, ToolType::STYLUS).x(300).y(400))
-                              .build()));
-}
-
 // --- InputDeviceMetricsCollectorTest ---
 
 class InputDeviceMetricsCollectorTest : public testing::Test, public InputDeviceMetricsLogger {
@@ -358,7 +94,13 @@
                            std::optional<UidUsageBreakdown> uidBreakdown = {}) {
         ASSERT_GE(mLoggedUsageSessions.size(), 1u);
         const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin();
-        ASSERT_EQ(info.getIdentifier(), loggedInfo.getIdentifier());
+        const auto& i = info.getIdentifier();
+        ASSERT_EQ(info.getId(), loggedInfo.deviceId);
+        ASSERT_EQ(i.vendor, loggedInfo.vendor);
+        ASSERT_EQ(i.product, loggedInfo.product);
+        ASSERT_EQ(i.version, loggedInfo.version);
+        ASSERT_EQ(i.bus, loggedInfo.bus);
+        ASSERT_EQ(info.getUsiVersion().has_value(), loggedInfo.isUsiStylus);
         ASSERT_EQ(duration, report.usageDuration);
         if (sourceBreakdown) {
             ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
@@ -389,12 +131,12 @@
     }
 
 private:
-    std::vector<std::tuple<InputDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
+    std::vector<std::tuple<MetricsDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
     nanoseconds mCurrentTime{TIME};
 
     nanoseconds getCurrentTime() override { return mCurrentTime; }
 
-    void logInputDeviceUsageReported(const InputDeviceInfo& info,
+    void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         mLoggedUsageSessions.emplace_back(info, report);
     }
diff --git a/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp b/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp
new file mode 100644
index 0000000..84ef52c
--- /dev/null
+++ b/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../InputDeviceMetricsSource.h"
+
+#include <NotifyArgsBuilders.h>
+
+#include <android/input.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <linux/input.h>
+
+#include <set>
+
+namespace android {
+
+namespace {
+
+constexpr auto ALL_USAGE_SOURCES = ftl::enum_range<InputDeviceUsageSource>();
+constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
+constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS;
+constexpr uint32_t KEY_SOURCES =
+        AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD;
+constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+} // namespace
+
+// --- InputDeviceMetricsSourceDeviceClassificationTest ---
+
+class DeviceClassificationFixture : public ::testing::Test,
+                                    public ::testing::WithParamInterface<InputDeviceUsageSource> {};
+
+TEST_P(DeviceClassificationFixture, ValidClassifications) {
+    const InputDeviceUsageSource usageSource = GetParam();
+
+    // Use a switch to ensure a test is added for all source classifications.
+    switch (usageSource) {
+        case InputDeviceUsageSource::UNKNOWN: {
+            ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
+                                                       .build()));
+
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::UNKNOWN};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_KEYBOARD)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::PALM)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::BUTTONS: {
+            ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::KEYBOARD: {
+            ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::DPAD: {
+            ASSERT_EQ(InputDeviceUsageSource::DPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_DPAD_CENTER)
+                                                       .build()));
+
+            ASSERT_EQ(InputDeviceUsageSource::DPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_DPAD_CENTER)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::GAMEPAD: {
+            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_BUTTON_A)
+                                                       .build()));
+
+            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_BUTTON_A)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::JOYSTICK: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::JOYSTICK};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_JOYSTICK)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+                                                       .axis(AMOTION_EVENT_AXIS_GAS, 1.f))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::MOUSE: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::MOUSE_CAPTURED: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE_CAPTURED};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+                                                AINPUT_SOURCE_MOUSE_RELATIVE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
+                                                       .x(100)
+                                                       .y(200)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 100)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCHPAD: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCHPAD_CAPTURED: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD_CAPTURED};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHPAD)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 1)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 2))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::ROTARY_ENCODER: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::ROTARY_ENCODER};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
+                                                AINPUT_SOURCE_ROTARY_ENCODER)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+                                                       .axis(AMOTION_EVENT_AXIS_SCROLL, 10)
+                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 10))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::STYLUS_DIRECT: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_DIRECT};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                STYLUS | TOUCHSCREEN)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::STYLUS_INDIRECT: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_INDIRECT};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                STYLUS | TOUCHSCREEN | AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::STYLUS_FUSED: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_FUSED};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                AINPUT_SOURCE_BLUETOOTH_STYLUS | TOUCHSCREEN)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCH_NAVIGATION: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCH_NAVIGATION};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+                                                AINPUT_SOURCE_TOUCH_NAVIGATION)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCHSCREEN: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200))
+                                      .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER)
+                                                       .x(300)
+                                                       .y(400))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TRACKBALL: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TRACKBALL};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
+                                                AINPUT_SOURCE_TRACKBALL)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 100)
+                                                       .axis(AMOTION_EVENT_AXIS_HSCROLL, 200))
+                                      .build()));
+            break;
+        }
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(InputDeviceMetricsSourceDeviceClassificationTest,
+                         DeviceClassificationFixture,
+                         ::testing::ValuesIn(ALL_USAGE_SOURCES.begin(), ALL_USAGE_SOURCES.end()),
+                         [](const testing::TestParamInfo<InputDeviceUsageSource>& testParamInfo) {
+                             return ftl::enum_string(testParamInfo.param);
+                         });
+
+TEST(InputDeviceMetricsSourceDeviceClassificationTest, MixedClassificationTouchscreenStylus) {
+    std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN,
+                                          InputDeviceUsageSource::STYLUS_DIRECT};
+    ASSERT_EQ(srcs,
+              getUsageSourcesForMotionArgs(
+                      MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN | STYLUS)
+                              .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(100).y(200))
+                              .pointer(PointerBuilder(/*id=*/2, ToolType::STYLUS).x(300).y(400))
+                              .build()));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 6ab263d..3c87f71 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,6 +16,7 @@
 
 #include "../dispatcher/InputDispatcher.h"
 #include "../BlockingQueue.h"
+#include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
 #include <android-base/properties.h>
@@ -23,10 +24,13 @@
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
+#include <com_android_input_flags.h>
 #include <fcntl.h>
+#include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
+#include <input/PrintTools.h>
 #include <linux/input.h>
 #include <sys/epoll.h>
 
@@ -49,6 +53,8 @@
 using namespace ftl::flag_operators;
 using testing::AllOf;
 
+namespace {
+
 // An arbitrary time value.
 static constexpr nsecs_t ARBITRARY_TIME = 1234;
 
@@ -69,6 +75,7 @@
 static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
 static constexpr int32_t ACTION_HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
+static constexpr int32_t ACTION_SCROLL = AMOTION_EVENT_ACTION_SCROLL;
 static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
 static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
 /**
@@ -107,17 +114,28 @@
 
 static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
 
+/**
+ * If we expect to receive the event, the timeout can be made very long. When the test are running
+ * correctly, we will actually never wait until the end of the timeout because the wait will end
+ * when the event comes in. Still, this value shouldn't be infinite. During development, a local
+ * change may cause the test to fail. This timeout should be short enough to not annoy so that the
+ * developer can see the failure quickly (on human scale).
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms;
+/**
+ * When no event is expected, we can have a very short timeout. A large value here would slow down
+ * the tests. In the unlikely event of system being too slow, the event may still be present but the
+ * timeout would complete before it is consumed. This would result in test flakiness. If this
+ * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this
+ * would get noticed and addressed quickly.
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms;
+
 static constexpr int expectedWallpaperFlags =
         AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
 using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
 
-struct PointF {
-    float x;
-    float y;
-    auto operator<=>(const PointF&) const = default;
-};
-
 /**
  * Return a DOWN key event with KEYCODE_A.
  */
@@ -130,87 +148,12 @@
     return event;
 }
 
-static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) {
-    ASSERT_EQ(expectedAction, receivedAction)
-            << "expected " << MotionEvent::actionToString(expectedAction) << ", got "
-            << MotionEvent::actionToString(receivedAction);
-}
-
-MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
-    bool matches = action == arg.getAction();
-    if (!matches) {
-        *result_listener << "expected action " << MotionEvent::actionToString(action)
-                         << ", but got " << MotionEvent::actionToString(arg.getAction());
-    }
-    if (action == AMOTION_EVENT_ACTION_DOWN) {
-        if (!matches) {
-            *result_listener << "; ";
-        }
-        *result_listener << "downTime should match eventTime for ACTION_DOWN events";
-        matches &= arg.getDownTime() == arg.getEventTime();
-    }
-    if (action == AMOTION_EVENT_ACTION_CANCEL) {
-        if (!matches) {
-            *result_listener << "; ";
-        }
-        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
-        matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
-    }
-    return matches;
-}
-
-MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
-    return arg.getDownTime() == downTime;
-}
-
-MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
-    return arg.getDisplayId() == displayId;
-}
-
-MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") {
-    return arg.getDeviceId() == deviceId;
-}
-
-MATCHER_P(WithSource, source, "InputEvent with specified source") {
-    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
-                     << inputEventSourceToString(arg.getSource());
-    return arg.getSource() == source;
-}
-
-MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
-    return arg.getFlags() == flags;
-}
-
-MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") {
-    if (arg.getPointerCount() != 1) {
-        *result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
-        return false;
-    }
-    return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y;
-}
-
-MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") {
-    return arg.getPointerCount() == pointerCount;
-}
-
-MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") {
-    // Build a map for the received pointers, by pointer id
-    std::map<int32_t /*pointerId*/, PointF> actualPointers;
-    for (size_t pointerIndex = 0; pointerIndex < arg.getPointerCount(); pointerIndex++) {
-        const int32_t pointerId = arg.getPointerId(pointerIndex);
-        actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)};
-    }
-    return pointers == actualPointers;
-}
-
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
-    InputDispatcherConfiguration mConfig;
-
     struct AnrResult {
         sp<IBinder> token{};
-        gui::Pid pid{-1};
+        gui::Pid pid{gui::Pid::INVALID};
     };
 
 public:
@@ -349,11 +292,6 @@
                    "signal";
     }
 
-    void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
-        mConfig.keyRepeatTimeout = timeout;
-        mConfig.keyRepeatDelay = delay;
-    }
-
     PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) {
         std::unique_lock lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
@@ -550,8 +488,6 @@
 
     void notifyVibratorState(int32_t deviceId, bool isOn) override {}
 
-    InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; }
-
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
         std::scoped_lock lock(mLock);
         switch (inputEvent.getType()) {
@@ -639,6 +575,7 @@
         mFilteredEvent = nullptr;
     }
 };
+} // namespace
 
 // --- InputDispatcherTest ---
 
@@ -650,7 +587,7 @@
     void SetUp() override {
         mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
         mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, STALE_EVENT_TIMEOUT);
-        mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
         // Start InputDispatcher thread
         ASSERT_EQ(OK, mDispatcher->start());
     }
@@ -691,7 +628,7 @@
     // Rejects undefined key actions.
     event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
                      INVALID_HMAC,
-                     /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
+                     /*action=*/-1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
                      ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
@@ -727,11 +664,11 @@
     ui::Transform identityTransform;
     // Rejects undefined motion actions.
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
-                     /*action*/ -1, 0, 0, edgeFlags, metaState, 0, classification,
+                     /*action=*/-1, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -743,7 +680,7 @@
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -755,7 +692,7 @@
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -767,7 +704,7 @@
                      0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -779,7 +716,7 @@
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -791,7 +728,7 @@
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 0, pointerProperties, pointerCoords);
+                     /*pointerCount=*/0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -802,7 +739,7 @@
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -815,7 +752,7 @@
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -827,7 +764,7 @@
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                     /*pointerCount=*/1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -841,7 +778,7 @@
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
                      ARBITRARY_TIME,
-                     /*pointerCount*/ 2, pointerProperties, pointerCoords);
+                     /*pointerCount=*/2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -870,7 +807,6 @@
 
 namespace {
 
-// --- InputDispatcherTest SetInputWindowTest ---
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms;
 // Default input dispatching timeout if there is no focused application or paused window
 // from which to determine an appropriate dispatching timeout.
@@ -902,9 +838,9 @@
         mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel));
     }
 
-    InputEvent* consume() {
+    InputEvent* consume(std::chrono::milliseconds timeout) {
         InputEvent* event;
-        std::optional<uint32_t> consumeSeq = receiveEvent(&event);
+        std::optional<uint32_t> consumeSeq = receiveEvent(timeout, &event);
         if (!consumeSeq) {
             return nullptr;
         }
@@ -916,7 +852,8 @@
      * Receive an event without acknowledging it.
      * Return the sequence number that could later be used to send finished signal.
      */
-    std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
+    std::optional<uint32_t> receiveEvent(std::chrono::milliseconds timeout,
+                                         InputEvent** outEvent = nullptr) {
         uint32_t consumeSeq;
         InputEvent* event;
 
@@ -926,7 +863,7 @@
             status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
                                         &event);
             std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > 100ms) {
+            if (elapsed > timeout) {
                 break;
             }
         }
@@ -966,7 +903,7 @@
     void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
-        InputEvent* event = consume();
+        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
 
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
@@ -981,7 +918,7 @@
         switch (expectedEventType) {
             case InputEventType::KEY: {
                 const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
-                EXPECT_EQ(expectedAction, keyEvent.getAction());
+                ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
                 if (expectedFlags.has_value()) {
                     EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
                 }
@@ -989,8 +926,7 @@
             }
             case InputEventType::MOTION: {
                 const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-                assertMotionAction(expectedAction, motionEvent.getAction());
-
+                ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
                 if (expectedFlags.has_value()) {
                     EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
                 }
@@ -1012,7 +948,7 @@
     }
 
     MotionEvent* consumeMotion() {
-        InputEvent* event = consume();
+        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
 
         if (event == nullptr) {
             ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
@@ -1033,7 +969,7 @@
     }
 
     void consumeFocusEvent(bool hasFocus, bool inTouchMode) {
-        InputEvent* event = consume();
+        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
         ASSERT_EQ(InputEventType::FOCUS, event->getType())
@@ -1047,7 +983,7 @@
     }
 
     void consumeCaptureEvent(bool hasCapture) {
-        const InputEvent* event = consume();
+        const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
         ASSERT_EQ(InputEventType::CAPTURE, event->getType())
@@ -1061,7 +997,7 @@
     }
 
     void consumeDragEvent(bool isExiting, float x, float y) {
-        const InputEvent* event = consume();
+        const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
         ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event;
@@ -1076,7 +1012,7 @@
     }
 
     void consumeTouchModeEvent(bool inTouchMode) {
-        const InputEvent* event = consume();
+        const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
         ASSERT_NE(nullptr, event) << mName.c_str()
                                   << ": consumer should have returned non-NULL event.";
         ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
@@ -1089,7 +1025,7 @@
     }
 
     void assertNoEvents() {
-        InputEvent* event = consume();
+        InputEvent* event = consume(CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
         if (event == nullptr) {
             return;
         }
@@ -1153,10 +1089,7 @@
         mInfo.name = name;
         mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
         mInfo.alpha = 1.0;
-        mInfo.frameLeft = 0;
-        mInfo.frameTop = 0;
-        mInfo.frameRight = WIDTH;
-        mInfo.frameBottom = HEIGHT;
+        mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
         mInfo.transform.set(0, 0);
         mInfo.globalScaleFactor = 1.0;
         mInfo.touchableRegion.clear();
@@ -1176,23 +1109,6 @@
         return handle;
     }
 
-    /**
-     * This is different from clone, because clone will make a "mirror" window - a window with the
-     * same token, but a different ID. The original window and the clone window are allowed to be
-     * sent to the dispatcher at the same time - they can coexist inside the dispatcher.
-     * This function will create a different object of WindowInfoHandle, but with the same
-     * properties as the original object - including the ID.
-     * You can use either the old or the new object to consume the events.
-     * IMPORTANT: The duplicated object is supposed to replace the original object, and not appear
-     * at the same time inside dispatcher.
-     */
-    sp<FakeWindowHandle> duplicate() {
-        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mName);
-        handle->mInfo = mInfo;
-        handle->mInputReceiver = mInputReceiver;
-        return handle;
-    }
-
     void setTouchable(bool touchable) {
         mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
     }
@@ -1254,10 +1170,7 @@
     void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
 
     void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
-        mInfo.frameLeft = frame.left;
-        mInfo.frameTop = frame.top;
-        mInfo.frameRight = frame.right;
-        mInfo.frameBottom = frame.bottom;
+        mInfo.frame = frame;
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(frame);
 
@@ -1289,6 +1202,25 @@
 
     void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
 
+    KeyEvent* consumeKey() {
+        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        if (event == nullptr) {
+            ADD_FAILURE() << "Consume failed : no event";
+            return nullptr;
+        }
+        if (event->getType() != InputEventType::KEY) {
+            ADD_FAILURE() << "Instead of key event, got " << *event;
+            return nullptr;
+        }
+        return static_cast<KeyEvent*>(event);
+    }
+
+    void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+        KeyEvent* keyEvent = consumeKey();
+        ASSERT_NE(nullptr, keyEvent) << "Did not get a key event, but expected " << matcher;
+        ASSERT_THAT(*keyEvent, matcher);
+    }
+
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
         consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags);
     }
@@ -1349,13 +1281,11 @@
 
     void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                               int32_t expectedFlags = 0) {
-        InputEvent* event = consume();
-        ASSERT_NE(nullptr, event);
-        ASSERT_EQ(InputEventType::MOTION, event->getType());
-        const MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
-        EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked());
-        EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getX());
-        EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getY());
+        MotionEvent* motionEvent = consumeMotion();
+        ASSERT_NE(nullptr, motionEvent);
+        EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent->getActionMasked());
+        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getX());
+        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getY());
     }
 
     void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
@@ -1399,7 +1329,7 @@
             ADD_FAILURE() << "Invalid receive event on window with no receiver";
             return std::nullopt;
         }
-        return mInputReceiver->receiveEvent(outEvent);
+        return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED, outEvent);
     }
 
     void finishEvent(uint32_t sequenceNum) {
@@ -1412,15 +1342,15 @@
         mInputReceiver->sendTimeline(inputEventId, timeline);
     }
 
-    InputEvent* consume() {
+    InputEvent* consume(std::chrono::milliseconds timeout) {
         if (mInputReceiver == nullptr) {
             return nullptr;
         }
-        return mInputReceiver->consume();
+        return mInputReceiver->consume(timeout);
     }
 
     MotionEvent* consumeMotion() {
-        InputEvent* event = consume();
+        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
         if (event == nullptr) {
             ADD_FAILURE() << "Consume failed : no event";
             return nullptr;
@@ -1467,8 +1397,70 @@
 
 std::atomic<int32_t> FakeWindowHandle::sId{1};
 
+class FakeMonitorReceiver {
+public:
+    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId) {
+        base::Result<std::unique_ptr<InputChannel>> channel =
+                dispatcher.createInputMonitor(displayId, name, MONITOR_PID);
+        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
+    }
+
+    sp<IBinder> getToken() { return mInputReceiver->getToken(); }
+
+    void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
+                                     expectedFlags);
+    }
+
+    std::optional<int32_t> receiveEvent() {
+        return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    }
+
+    void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); }
+
+    void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
+                                     expectedDisplayId, expectedFlags);
+    }
+
+    void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
+                                     expectedDisplayId, expectedFlags);
+    }
+
+    void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
+                                     expectedDisplayId, expectedFlags);
+    }
+
+    void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeMotionEvent(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                      WithDisplayId(expectedDisplayId),
+                      WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
+    }
+
+    void consumeMotionPointerDown(int32_t pointerIdx) {
+        int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
+                                     /*expectedFlags=*/0);
+    }
+
+    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+        mInputReceiver->consumeMotionEvent(matcher);
+    }
+
+    MotionEvent* consumeMotion() { return mInputReceiver->consumeMotion(); }
+
+    void assertNoEvents() { mInputReceiver->assertNoEvents(); }
+
+private:
+    std::unique_ptr<FakeInputReceiver> mInputReceiver;
+};
+
 static InputEventInjectionResult injectKey(
-        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t repeatCount,
+        InputDispatcher& dispatcher, int32_t action, int32_t repeatCount,
         int32_t displayId = ADISPLAY_ID_NONE,
         InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -1486,10 +1478,19 @@
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
     }
     // Inject event until dispatch out.
-    return dispatcher->injectInputEvent(&event, targetUid, syncMode, injectionTimeout, policyFlags);
+    return dispatcher.injectInputEvent(&event, targetUid, syncMode, injectionTimeout, policyFlags);
 }
 
-static InputEventInjectionResult injectKeyDown(const std::unique_ptr<InputDispatcher>& dispatcher,
+static void assertInjectedKeyTimesOut(InputDispatcher& dispatcher) {
+    InputEventInjectionResult result =
+            injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_NONE,
+                      InputEventInjectionSync::WAIT_FOR_RESULT, CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
+    if (result != InputEventInjectionResult::TIMED_OUT) {
+        FAIL() << "Injection should have timed out, but got " << ftl::enum_string(result);
+    }
+}
+
+static InputEventInjectionResult injectKeyDown(InputDispatcher& dispatcher,
                                                int32_t displayId = ADISPLAY_ID_NONE) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId);
 }
@@ -1497,30 +1498,30 @@
 // Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without
 // sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it
 // has to be woken up to process the repeating key.
-static InputEventInjectionResult injectKeyDownNoRepeat(
-        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyDownNoRepeat(InputDispatcher& dispatcher,
+                                                       int32_t displayId = ADISPLAY_ID_NONE) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId,
                      InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT,
                      /*allowKeyRepeat=*/false);
 }
 
-static InputEventInjectionResult injectKeyUp(const std::unique_ptr<InputDispatcher>& dispatcher,
+static InputEventInjectionResult injectKeyUp(InputDispatcher& dispatcher,
                                              int32_t displayId = ADISPLAY_ID_NONE) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId);
 }
 
 static InputEventInjectionResult injectMotionEvent(
-        const std::unique_ptr<InputDispatcher>& dispatcher, const MotionEvent& event,
+        InputDispatcher& dispatcher, const MotionEvent& event,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
         InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT,
         std::optional<gui::Uid> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
-    return dispatcher->injectInputEvent(&event, targetUid, injectionMode, injectionTimeout,
-                                        policyFlags);
+    return dispatcher.injectInputEvent(&event, targetUid, injectionMode, injectionTimeout,
+                                       policyFlags);
 }
 
 static InputEventInjectionResult injectMotionEvent(
-        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t source,
-        int32_t displayId, const PointF& position = {100, 200},
+        InputDispatcher& dispatcher, int32_t action, int32_t source, int32_t displayId,
+        const PointF& position = {100, 200},
         const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                         AMOTION_EVENT_INVALID_CURSOR_POSITION},
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -1544,14 +1545,14 @@
                              targetUid, policyFlags);
 }
 
-static InputEventInjectionResult injectMotionDown(
-        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t source, int32_t displayId,
-        const PointF& location = {100, 200}) {
+static InputEventInjectionResult injectMotionDown(InputDispatcher& dispatcher, int32_t source,
+                                                  int32_t displayId,
+                                                  const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
 }
 
-static InputEventInjectionResult injectMotionUp(const std::unique_ptr<InputDispatcher>& dispatcher,
-                                                int32_t source, int32_t displayId,
+static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int32_t source,
+                                                int32_t displayId,
                                                 const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
 }
@@ -1560,8 +1561,8 @@
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
     NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, POLICY_FLAG_PASS_TO_USER, action, /* flags */ 0, AKEYCODE_A,
-                       KEY_A, AMETA_NONE, currentTime);
+                       displayId, POLICY_FLAG_PASS_TO_USER, action, /*flags=*/0, AKEYCODE_A, KEY_A,
+                       AMETA_NONE, currentTime);
 
     return args;
 }
@@ -1571,7 +1572,7 @@
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
     NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /* flags */ 0, AKEYCODE_C, KEY_C, AMETA_META_ON,
+                       displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, AMETA_META_ON,
                        currentTime);
 
     return args;
@@ -1582,7 +1583,7 @@
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
     NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /* flags */ 0, AKEYCODE_ASSIST, KEY_ASSISTANT,
+                       displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, KEY_ASSISTANT,
                        AMETA_NONE, currentTime);
 
     return args;
@@ -1612,12 +1613,12 @@
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid motion event.
     NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId,
-                          POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0,
-                          AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
+                          POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, /*flags=*/0,
+                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
-                          pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+                          pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                          AMOTION_EVENT_INVALID_CURSOR_POSITION, currentTime, /* videoFrames */ {});
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION, currentTime, /*videoFrames=*/{});
 
     return args;
 }
@@ -1647,7 +1648,7 @@
             sp<FakeWindowHandle>::make(application, mDispatcher,
                                        "Window that breaks its input channel", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Window closes its channel, but the window remains.
     window->destroyReceiver();
@@ -1659,9 +1660,9 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
@@ -1673,10 +1674,10 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     // Inject a MotionEvent to an unknown display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
@@ -1684,8 +1685,8 @@
 }
 
 /**
- * Calling setInputWindows once should not cause any issues.
- * This test serves as a sanity check for the next test, where setInputWindows is
+ * Calling onWindowInfosChanged once should not cause any issues.
+ * This test serves as a sanity check for the next test, where onWindowInfosChanged is
  * called twice.
  */
 TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) {
@@ -1694,9 +1695,9 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1705,7 +1706,7 @@
 }
 
 /**
- * Calling setInputWindows twice, with the same info, should not cause any issues.
+ * Calling onWindowInfosChanged twice, with the same info, should not cause any issues.
  */
 TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -1713,10 +1714,10 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1732,9 +1733,10 @@
     sp<FakeWindowHandle> windowSecond =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Top window should receive the touch down event. Second window should not receive anything.
@@ -1758,9 +1760,10 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1769,7 +1772,7 @@
     wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1777,7 +1780,7 @@
     wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Now the foreground window goes away, but the wallpaper stays
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
     foregroundWindow->consumeMotionCancel();
     // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
     wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
@@ -1793,7 +1796,7 @@
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     // First touch pointer down on right window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
@@ -1813,7 +1816,7 @@
     window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
 
     // Remove the window. The gesture should be canceled
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
     const std::map<int32_t, PointF> expectedPointers{{1, PointF{110, 100}}};
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedPointers)));
@@ -1836,9 +1839,10 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1847,7 +1851,7 @@
     wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1860,7 +1864,7 @@
 
     // Now the foreground window goes away, but the wallpaper stays, even though its channel
     // is no longer valid.
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
     foregroundWindow->consumeMotionCancel();
 }
 
@@ -1884,11 +1888,12 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
 
     // Touch down on top window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1904,7 +1909,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1920,20 +1925,19 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     foregroundWindow->consumeMotionPointerUp(0);
     wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .displayId(ADISPLAY_ID_DEFAULT)
                                         .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                                        .pointer(PointerBuilder(/* id */ 1,
-                                                                ToolType::FINGER)
+                                        .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
                                                          .x(100)
                                                          .y(100))
                                         .build(),
@@ -1968,12 +1972,15 @@
     wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
     wallpaperWindow->setIsWallpaper(true);
 
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo(), *wallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
 
     // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -1989,7 +1996,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(300).y(100))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -2000,7 +2007,8 @@
                                               expectedWallpaperFlags);
 
     // Now, leftWindow, which received the first finger, disappears.
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightWindow, wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     leftWindow->consumeMotionCancel();
     // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
     wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
@@ -2014,7 +2022,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(310).y(110))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
     rightWindow->consumeMotionMove();
 
@@ -2044,12 +2052,15 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo(), *wallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
 
     // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -2059,7 +2070,7 @@
 
     // Move to right window, the left window should receive cancel.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {201, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -2096,7 +2107,7 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
     const int32_t touchDeviceId = 4;
 
     // Two pointers down
@@ -2169,7 +2180,8 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
     // All times need to start at the current time, otherwise the dispatcher will drop the events as
     // stale.
     const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -2177,86 +2189,74 @@
     const int32_t touchDeviceId = 4;
     // Move the cursor from right
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .deviceId(mouseDeviceId)
                                         .downTime(baseTime + 10)
                                         .eventTime(baseTime + 20)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(300)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(100))
                                         .build()));
     rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // .. to the left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .deviceId(mouseDeviceId)
                                         .downTime(baseTime + 10)
                                         .eventTime(baseTime + 30)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(110)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(100))
                                         .build()));
     rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
     // Now tap the left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 40)
                                         .eventTime(baseTime + 40)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
     // release tap
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 40)
                                         .eventTime(baseTime + 50)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
 
     // Tap the window on the right
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 60)
                                         .eventTime(baseTime + 60)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(300)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                         .build()));
     rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
     // release tap
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
                                         .downTime(baseTime + 60)
                                         .eventTime(baseTime + 70)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(300)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                         .build()));
     rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
 
@@ -2281,7 +2281,7 @@
     window->setFrame(Rect(0, 0, 200, 200));
 
     // Only a single window is present at first
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Start hovering in the window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
@@ -2300,7 +2300,8 @@
     obscuringWindow->setNoInputChannel(true);
     obscuringWindow->setFocusable(false);
     obscuringWindow->setAlpha(1.0);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // While this new obscuring window is present, the hovering is stopped
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
@@ -2309,7 +2310,7 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 
     // Now the obscuring window goes away.
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // And a new hover gesture starts.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
@@ -2329,7 +2330,7 @@
     window->setFrame(Rect(0, 0, 200, 200));
 
     // Only a single window is present at first
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Start hovering in the window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
@@ -2348,7 +2349,8 @@
     obscuringWindow->setNoInputChannel(true);
     obscuringWindow->setFocusable(false);
     obscuringWindow->setAlpha(1.0);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // While this new obscuring window is present, the hovering continues. The event can't go to the
     // bottom window due to obstructed touches, so it should generate HOVER_EXIT for that window.
@@ -2359,7 +2361,7 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 
     // Now the obscuring window goes away.
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Hovering continues in the same position. The hovering pointer re-enters the bottom window,
     // so it should generate a HOVER_ENTER
@@ -2376,13 +2378,347 @@
 }
 
 /**
+ * Hover mouse over a window, and then send ACTION_SCROLL. Ensure both the hover and the scroll
+ * events are delivered to the window.
+ */
+TEST_F(InputDispatcherTest, HoverMoveAndScroll) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Start hovering in the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+    // Scroll with the mouse
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_SCROLL, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL));
+}
+
+using InputDispatcherMultiDeviceTest = InputDispatcherTest;
+
+/**
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is dropped, because stylus should be preferred over touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+
+    // Touch move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    // Touch is ignored because stylus is already down
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
+    window->assertNoEvents();
+}
+
+/**
+ * One window and one spy window. Stylus down on the window. Next, touch from another device goes
+ * down. Ensure that touch is dropped, because stylus should be preferred over touch.
+ * Similar test as above, but with added SPY window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+
+    // Touch move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+
+    // Touch is ignored because stylus is already down
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                        WithCoords(101, 111)));
+
+    window->assertNoEvents();
+    spyWindow->assertNoEvents();
+}
+
+/**
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because stylus hover should be ignored.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+
+    // Stylus hover is canceled because touch is down
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
+                                     WithCoords(140, 145)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(141, 146)));
+
+    // Subsequent stylus movements are ignored
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+
+    // but subsequent touches continue to be delivered
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+}
+
+/**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is not canceled, because stylus hover should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    // Stylus hover movement is dropped
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    // Subsequent touch movements are delivered correctly
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+}
+
+/**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
+ * become active.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t stylusDeviceId1 = 3;
+    constexpr int32_t stylusDeviceId2 = 5;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+    // Second stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+                                      .build());
+
+    // First stylus is canceled, second one takes over.
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+    // Subsequent stylus movements are delivered correctly
+    window->assertNoEvents();
+}
+
+/**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that is canceled, because stylus down should be preferred over touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
  * down. Then, on the left window, also place second touch pointer down.
  * This test tries to reproduce a crash.
  * In the buggy implementation, second pointer down on the left window would cause a crash.
  */
-TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
@@ -2392,11 +2728,11 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
 
     const int32_t touchDeviceId = 4;
     const int32_t mouseDeviceId = 6;
-    NotifyMotionArgs args;
 
     // Start hovering over the left window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
@@ -2432,7 +2768,7 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    leftWindow->assertNoEvents();
 
     rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
@@ -2442,6 +2778,11 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
                                       .build());
+    // Since this is now a new splittable pointer going down on the left window, and it's coming
+    // from a different device, the current gesture in the left window (pointer down) should first
+    // be canceled.
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId)));
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
     // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
@@ -2455,6 +2796,214 @@
 }
 
 /**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered on the left window and stylus is hovered on the right window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 3;
+    const int32_t mouseDeviceId = 6;
+
+    // Start hovering over the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Stylus hovered on right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(300).y(100))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Subsequent HOVER_MOVE events are dispatched correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(310).y(110))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * Three windows: a window on the left and a window on the right.
+ * And a spy window that's positioned above all of them.
+ * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
+ * Check the stream that's received by the spy.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 400, 400));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 1;
+    const int32_t touchDeviceId = 2;
+
+    // Stylus down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Spy window does not receive touch events, because stylus events take precedence, and it
+    // already has an active stylus gesture.
+
+    // Stylus movements continue. They should be delivered to the left window and to the spy window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Further MOVE events keep going to the right window only
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spyWindow->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * Three windows: a window on the left, a window on the right, and a spy window positioned above
+ * both.
+ * Check hover in left window and touch down in the right window.
+ * At first, spy should receive hover, but the touch down should cancel hovering inside spy.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 400, 400));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 1;
+    const int32_t touchDeviceId = 2;
+
+    // Stylus hover on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on the right window. Spy doesn't receive this touch because it already has
+    // stylus hovering there.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Stylus movements continue. They should be delivered to the left window only.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Touch movements continue. They should be delivered to the right window and to the spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spyWindow->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * On a single window, use two different devices: mouse and touch.
  * Touch happens first, with two pointers going down, and then the first pointer leaving.
  * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL.
@@ -2462,17 +3011,16 @@
  * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not
  * represent a new gesture.
  */
-TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) {
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     const int32_t touchDeviceId = 4;
     const int32_t mouseDeviceId = 6;
-    NotifyMotionArgs args;
 
     // First touch pointer down
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
@@ -2521,7 +3069,17 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
                                       .build());
-    // The pointer_down event should be ignored
+    // Since we already canceled this touch gesture, it will be ignored until a completely new
+    // gesture is started. This is easier to implement than trying to keep track of the new pointer
+    // and generating an ACTION_DOWN instead of ACTION_POINTER_DOWN.
+    // However, mouse movements should continue to work.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
     window->assertNoEvents();
 }
 
@@ -2529,25 +3087,22 @@
  * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels
  * the injected event.
  */
-TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) {
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     const int32_t touchDeviceId = 4;
-    NotifyMotionArgs args;
     // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after
     // completion.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                         .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(50)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
                                         .build()));
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
@@ -2578,7 +3133,7 @@
  * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
  * In the buggy implementation, second finger down on the left window would cause a crash.
  */
-TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
@@ -2588,41 +3143,36 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
 
     const int32_t mouseDeviceId = 6;
     const int32_t touchDeviceId = 4;
     // Hover over the left window. Keep the cursor there.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
                                         .deviceId(mouseDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(50)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
                                         .build()));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // Tap on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
@@ -2630,27 +3180,21 @@
 
     // First finger down on right window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(300)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                         .build()));
     rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
     // Second finger down on the left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(300)
-                                                         .y(100))
-                                        .pointer(PointerBuilder(1, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                        .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
                                         .build()));
     leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
     rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
@@ -2665,78 +3209,66 @@
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
-TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     const int32_t stylusDeviceId = 5;
     const int32_t touchDeviceId = 4;
     // Start hovering with stylus
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
-                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                   AINPUT_SOURCE_STYLUS)
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::STYLUS)
-                                                         .x(50)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
                                         .build()));
-    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
 
     // Finger down on the window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
-                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
-                                                   AINPUT_SOURCE_TOUCHSCREEN)
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
-    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    // The touch device should cause hover to stop!
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Try to continue hovering with stylus. Since we are already down, injection should fail
-    ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionEvent(mDispatcher,
+    // Continue hovering with stylus.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::STYLUS)
-                                                         .x(50)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
                                         .build()));
-    // No event should be sent. This event should be ignored because a pointer from another device
-    // is already down.
+    // Hovers are now ignored
 
     // Lift up the finger
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(touchDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
 
-    // Now that the touch is gone, stylus hovering should start working again
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
-                                        .pointer(PointerBuilder(0, ToolType::STYLUS)
-                                                         .x(50)
-                                                         .y(50))
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
                                         .build()));
-    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
-    // No more events
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                     WithDeviceId(stylusDeviceId)));
     window->assertNoEvents();
 }
 
@@ -2757,7 +3289,7 @@
     window->setNoInputChannel(true);
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Start hovering with stylus
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
@@ -2815,7 +3347,7 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     const int32_t mouseDeviceId = 7;
     const int32_t touchDeviceId = 4;
@@ -2910,7 +3442,7 @@
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", DISPLAY_ID);
 
-    mDispatcher->setInputWindows({{DISPLAY_ID, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Touch down on the empty space
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}}));
@@ -2938,7 +3470,7 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
     window2->setTouchableRegion(Region{{100, 0, 200, 100}});
 
-    mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
 
     // Touch down on the non-touchable window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}));
@@ -2966,28 +3498,27 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
     window2->setTouchableRegion(Region{{100, 0, 200, 100}});
 
-    mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
 
     // Touch down on the first window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}));
 
     mDispatcher->waitForIdle();
-    InputEvent* inputEvent1 = window1->consume();
-    ASSERT_NE(inputEvent1, nullptr);
+
+    MotionEvent* motionEvent1 = window1->consumeMotion();
+    ASSERT_NE(motionEvent1, nullptr);
     window2->assertNoEvents();
-    MotionEvent& motionEvent1 = static_cast<MotionEvent&>(*inputEvent1);
-    nsecs_t downTimeForWindow1 = motionEvent1.getDownTime();
-    ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime());
+    nsecs_t downTimeForWindow1 = motionEvent1->getDownTime();
+    ASSERT_EQ(motionEvent1->getDownTime(), motionEvent1->getEventTime());
 
     // Now touch down on the window with another pointer
     mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}));
     mDispatcher->waitForIdle();
-    InputEvent* inputEvent2 = window2->consume();
-    ASSERT_NE(inputEvent2, nullptr);
-    MotionEvent& motionEvent2 = static_cast<MotionEvent&>(*inputEvent2);
-    nsecs_t downTimeForWindow2 = motionEvent2.getDownTime();
+    MotionEvent* motionEvent2 = window2->consumeMotion();
+    ASSERT_NE(motionEvent2, nullptr);
+    nsecs_t downTimeForWindow2 = motionEvent2->getDownTime();
     ASSERT_NE(downTimeForWindow1, downTimeForWindow2);
-    ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime());
+    ASSERT_EQ(motionEvent2->getDownTime(), motionEvent2->getEventTime());
 
     // Now move the pointer on the second window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}));
@@ -3026,11 +3557,12 @@
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowLeft, windowRight}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
 
     // Start cursor position in right window so that we can move the cursor to left window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400))
@@ -3039,7 +3571,7 @@
 
     // Move cursor into left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3049,7 +3581,7 @@
 
     // Inject a series of mouse events for a mouse click
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3058,7 +3590,7 @@
     windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
@@ -3068,7 +3600,7 @@
     windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
@@ -3078,7 +3610,7 @@
     windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3087,7 +3619,7 @@
 
     // Move mouse cursor back to right window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400))
@@ -3110,7 +3642,7 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     const int32_t touchDeviceId = 4;
     const int32_t mouseDeviceId = 6;
@@ -3171,16 +3703,14 @@
     window->setFrame(Rect(0, 0, 600, 800));
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Send mouse cursor to the window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
                                         .build()));
 
     window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
@@ -3205,26 +3735,22 @@
     window->setFrame(Rect(0, 0, 600, 800));
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Send mouse cursor to the window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(100)
-                                                         .y(100))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
                                         .build()));
 
     // Move mouse cursor
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(110)
-                                                         .y(110))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
                                         .build()));
 
     window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
@@ -3237,13 +3763,11 @@
                                         WithSource(AINPUT_SOURCE_MOUSE)));
     // Touch down on the window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(200)
-                                                         .y(200))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
                                         .build()));
     window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
                                      WithSource(AINPUT_SOURCE_MOUSE)));
@@ -3261,13 +3785,11 @@
 
     // Touch UP on the window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(200)
-                                                         .y(200))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
                                         .build()));
     spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                         WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
@@ -3278,13 +3800,11 @@
 
     // One more tap - DOWN
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(250)
-                                                         .y(250))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
                                         .build()));
     window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                                      WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
@@ -3293,13 +3813,11 @@
 
     // Touch UP on the window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .deviceId(SECOND_DEVICE_ID)
-                                        .pointer(PointerBuilder(0, ToolType::FINGER)
-                                                         .x(250)
-                                                         .y(250))
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
                                         .build()));
     window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                      WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
@@ -3320,10 +3838,10 @@
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3331,7 +3849,7 @@
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
     // Inject a series of mouse events for a mouse click
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3340,7 +3858,7 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
@@ -3350,7 +3868,7 @@
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
@@ -3360,7 +3878,7 @@
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3370,7 +3888,7 @@
     // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the
     // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT,
                                                    AINPUT_SOURCE_MOUSE)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
@@ -3390,42 +3908,44 @@
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(300)
-                                                         .y(400))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // Remove the window, but keep the channel.
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
 }
 
 /**
  * Test that invalid HOVER events sent by accessibility do not cause a fatal crash.
  */
-TEST_F(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash) {
+TEST_F_WITH_FLAGS(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                       a11y_crash_on_inconsistent_event_stream))) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
+
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     MotionEventBuilder hoverEnterBuilder =
             MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
                     .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                     .addFlag(AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, hoverEnterBuilder.build()));
+              injectMotionEvent(*mDispatcher, hoverEnterBuilder.build()));
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, hoverEnterBuilder.build()));
+              injectMotionEvent(*mDispatcher, hoverEnterBuilder.build()));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
     window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 }
@@ -3439,7 +3959,7 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     const int32_t mouseDeviceId = 7;
     const int32_t touchDeviceId = 4;
@@ -3474,23 +3994,18 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    // Inject a hover_move from mouse.
-    NotifyMotionArgs motionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE,
-                               ADISPLAY_ID_DEFAULT, {{50, 50}});
-    motionArgs.xCursorPosition = 50;
-    motionArgs.yCursorPosition = 50;
-    mDispatcher->notifyMotion(motionArgs);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                                      .build());
     ASSERT_NO_FATAL_FAILURE(
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                              WithSource(AINPUT_SOURCE_MOUSE))));
 
     // Tap on the window
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {{10, 10}}));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+                                      .build());
     ASSERT_NO_FATAL_FAILURE(
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
                                              WithSource(AINPUT_SOURCE_MOUSE))));
@@ -3499,8 +4014,9 @@
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                                              WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
 
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{10, 10}}));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+                                      .build());
     ASSERT_NO_FATAL_FAILURE(
             window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                              WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
@@ -3517,40 +4033,36 @@
                                        SECOND_DISPLAY_ID);
     windowSecondDisplay->setFrame(Rect(0, 0, 600, 800));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}},
-                                  {SECOND_DISPLAY_ID, {windowSecondDisplay}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowDefaultDisplay->getInfo(), *windowSecondDisplay->getInfo()}, {}, 0, 0});
 
     // Set cursor position in window in default display and check that hover enter and move
     // events are generated.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .displayId(ADISPLAY_ID_DEFAULT)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(300)
-                                                         .y(600))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(600))
                                         .build()));
     windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // Remove all windows in secondary display and check that no event happens on window in
     // primary display.
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}}, {SECOND_DISPLAY_ID, {}}});
+    mDispatcher->onWindowInfosChanged({{*windowDefaultDisplay->getInfo()}, {}, 0, 0});
+
     windowDefaultDisplay->assertNoEvents();
 
     // Move cursor position in window in default display and check that only hover move
     // event is generated and not hover enter event.
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}},
-                                  {SECOND_DISPLAY_ID, {windowSecondDisplay}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowDefaultDisplay->getInfo(), *windowSecondDisplay->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
                                         .displayId(ADISPLAY_ID_DEFAULT)
-                                        .pointer(PointerBuilder(0, ToolType::MOUSE)
-                                                         .x(400)
-                                                         .y(700))
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(400).y(700))
                                         .build()));
     windowDefaultDisplay->consumeMotionEvent(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
@@ -3570,12 +4082,13 @@
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowLeft, windowRight}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
 
     // Inject an event with coordinate in the area of right window, with mouse cursor in the area of
     // left window. This event should be dispatched to the left window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
                                 ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400}));
     windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     windowRight->assertNoEvents();
@@ -3587,7 +4100,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFocusable(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -3609,7 +4122,7 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
@@ -3629,7 +4142,7 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(10))
@@ -3654,7 +4167,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFocusable(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -3679,7 +4192,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFocusable(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -3704,17 +4217,17 @@
  */
 TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "First Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect{0, 0, 100, 100});
 
     sp<FakeWindowHandle> outsideWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
                                        ADISPLAY_ID_DEFAULT);
     outsideWindow->setFrame(Rect{100, 100, 200, 200});
     outsideWindow->setWatchOutsideTouch(true);
     // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*outsideWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Tap on first window.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -3747,7 +4260,8 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Third Window",
                                        ADISPLAY_ID_DEFAULT);
     thirdWindow->setFrame(Rect{200, 200, 300, 300});
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, secondWindow, thirdWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*window->getInfo(), *secondWindow->getInfo(), *thirdWindow->getInfo()}, {}, 0, 0});
 
     // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -3816,7 +4330,7 @@
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -3829,7 +4343,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -3841,6 +4355,44 @@
     EXPECT_EQ(-10, event->getY(1)); // -50 + 40
 }
 
+/**
+ * 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.
+ */
+TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left splittable Window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setPreventSplitting(false);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right non-splittable Window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWindow->setPreventSplitting(true);
+    rightWindow->setFrame(Rect(100, 100, 200, 200));
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    // Touch down on left, splittable window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .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();
+}
+
 TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
@@ -3852,7 +4404,7 @@
     trustedOverlay->setSpy(true);
     trustedOverlay->setTrustedOverlay(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {trustedOverlay, window}}});
+    mDispatcher->onWindowInfosChanged({{*trustedOverlay->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Start a three-finger touchpad swipe
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
@@ -3914,7 +4466,7 @@
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Start a three-finger touchpad swipe
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
@@ -3964,16 +4516,14 @@
 /**
  * Send a two-pointer gesture to a single window. The window's orientation changes in response to
  * the first pointer.
- * Ensure that the second pointer is not sent to the window.
- *
- * The subsequent gesture should be correctly delivered to the window.
+ * Ensure that the second pointer and the subsequent gesture is correctly delivered to the window.
  */
 TEST_F(InputDispatcherTest, MultiplePointersWithRotatingWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
@@ -3984,19 +4534,10 @@
 
     window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
-    // We need a new window object for the same window, because dispatcher will store objects by
-    // reference. That means that the testing code and the dispatcher will refer to the same shared
-    // object. Calling window->setTransform here would affect dispatcher's comparison
-    // of the old window to the new window, since both the old window and the new window would be
-    // updated to the same value.
-    sp<FakeWindowHandle> windowDup = window->duplicate();
-
     // Change the transform so that the orientation is now different from original.
-    windowDup->setWindowTransform(0, -1, 1, 0);
+    window->setWindowTransform(0, -1, 1, 0);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDup}}});
-
-    window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .downTime(baseTime + 10)
@@ -4005,26 +4546,33 @@
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
                                       .build());
 
-    // Finish the gesture and start a new one. Ensure the new gesture is sent to the window
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+    // Finish the gesture and start a new one. Ensure all events are sent to the window.
     mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
                                       .downTime(baseTime + 10)
                                       .eventTime(baseTime + 40)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
                                       .build());
+
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
                                       .downTime(baseTime + 10)
                                       .eventTime(baseTime + 50)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                       .build());
 
+    window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .downTime(baseTime + 60)
                                       .eventTime(baseTime + 60)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
                                       .build());
 
-    windowDup->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 }
 
 /**
@@ -4060,7 +4608,7 @@
 
     // Send down to ADISPLAY_ID_DEFAULT
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -4165,7 +4713,7 @@
     // Send down to the first window. The point is represented in the logical display space. The
     // point is selected so that if the hit test was done in logical display space, then it would
     // end up in the incorrect window.
-    injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                      PointF{75 * 2, 55 * 4});
 
     firstWindow->consumeMotionDown();
@@ -4192,7 +4740,7 @@
                                 .build();
     event.transform(matrix);
 
-    injectMotionEvent(mDispatcher, event, INJECT_EVENT_TIMEOUT,
+    injectMotionEvent(*mDispatcher, event, INJECT_EVENT_TIMEOUT,
                       InputEventInjectionSync::WAIT_FOR_RESULT);
 
     firstWindow->consumeMotionDown();
@@ -4223,6 +4771,151 @@
     EXPECT_EQ(80, event->getY(0));
 }
 
+TEST_F(InputDispatcherDisplayProjectionTest, CancelMotionWithCorrectCoordinates) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+    // The monitor will always receive events in the logical display's coordinate space, because
+    // it does not have a window.
+    FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ADISPLAY_ID_DEFAULT};
+
+    // Send down to the first window.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+    firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
+    monitor.consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
+
+    // Second pointer goes down on second window.
+    mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT,
+                                                 {PointF{50, 100}, PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80)));
+    const std::map<int32_t, PointF> expectedMonitorPointers{{0, PointF{100, 400}},
+                                                            {1, PointF{300, 880}}};
+    monitor.consumeMotionEvent(
+            AllOf(WithMotionAction(POINTER_1_DOWN), WithPointers(expectedMonitorPointers)));
+
+    mDispatcher->cancelCurrentTouch();
+
+    firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 400)));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 80)));
+    monitor.consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedMonitorPointers)));
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeDownWithCorrectCoordinates) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    // Send down to the first window.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+    firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
+
+    // The pointer is transferred to the second window, and the second window receives it in the
+    // correct coordinate space.
+    mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
+    firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 400)));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(-100, -400)));
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverEnterExitWithCorrectCoordinates) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    // Send hover move to the second window, and ensure it shows up as hover enter.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                           WithCoords(100, 80), WithRawCoords(300, 880)));
+
+    // Touch down at the same location and ensure a hover exit is synthesized.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+                                           WithRawCoords(300, 880)));
+    secondWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80), WithRawCoords(300, 880)));
+    secondWindow->assertNoEvents();
+    firstWindow->assertNoEvents();
+}
+
+// Same as above, but while the window is being mirrored.
+TEST_F(InputDispatcherDisplayProjectionTest,
+       SynthesizeHoverEnterExitWithCorrectCoordinatesWhenMirrored) {
+    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);
+
+    sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+    secondWindowClone->setWindowTransform(1.1, 2.2, 3.3, 4.4);
+    addWindow(secondWindowClone);
+
+    // Send hover move to the second window, and ensure it shows up as hover enter.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                           WithCoords(100, 80), WithRawCoords(300, 880)));
+
+    // Touch down at the same location and ensure a hover exit is synthesized for the correct
+    // display.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+                                           WithRawCoords(300, 880)));
+    secondWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80), WithRawCoords(300, 880)));
+    secondWindow->assertNoEvents();
+    firstWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverCancelationWithCorrectCoordinates) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    // Send hover enter to second window
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                           WithCoords(100, 80), WithRawCoords(300, 880)));
+
+    mDispatcher->cancelCurrentTouch();
+
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+                                           WithRawCoords(300, 880)));
+    secondWindow->assertNoEvents();
+    firstWindow->assertNoEvents();
+}
+
+// Same as above, but while the window is being mirrored.
+TEST_F(InputDispatcherDisplayProjectionTest,
+       SynthesizeHoverCancelationWithCorrectCoordinatesWhenMirrored) {
+    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);
+
+    sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+    secondWindowClone->setWindowTransform(1.1, 2.2, 3.3, 4.4);
+    addWindow(secondWindowClone);
+
+    // Send hover enter to second window
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                           WithCoords(100, 80), WithRawCoords(300, 880),
+                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    mDispatcher->cancelCurrentTouch();
+
+    // Ensure the cancelation happens with the correct displayId and the correct coordinates.
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+                                           WithRawCoords(300, 880),
+                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    secondWindow->assertNoEvents();
+    firstWindow->assertNoEvents();
+}
+
 /** Ensure consistent behavior of InputDispatcher in all orientations. */
 class InputDispatcherDisplayOrientationFixture
       : public InputDispatcherDisplayProjectionTest,
@@ -4319,7 +5012,8 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaper->setIsWallpaper(true);
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindow->getInfo(), *secondWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -4374,7 +5068,8 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
 
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -4417,7 +5112,8 @@
     secondWindow->setPreventSplitting(true);
 
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -4479,8 +5175,11 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
     wallpaper2->setIsWallpaper(true);
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}});
+    mDispatcher->onWindowInfosChanged({{*firstWindow->getInfo(), *wallpaper1->getInfo(),
+                                        *secondWindow->getInfo(), *wallpaper2->getInfo()},
+                                       {},
+                                       0,
+                                       0});
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -4543,7 +5242,8 @@
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
 
     PointF pointInFirst = {300, 200};
     PointF pointInSecond = {300, 600};
@@ -4604,7 +5304,8 @@
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
 
     PointF pointInFirst = {300, 200};
     PointF pointInSecond = {300, 600};
@@ -4672,13 +5373,16 @@
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
 
     // Update window info, let it find window handle of second display first.
-    mDispatcher->setInputWindows(
-            {{SECOND_DISPLAY_ID, {firstWindowInSecondary, secondWindowInSecondary}},
-             {ADISPLAY_ID_DEFAULT,
-              {mirrorWindowInPrimary, firstWindowInPrimary, secondWindowInPrimary}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindowInSecondary->getInfo(), *secondWindowInSecondary->getInfo(),
+              *mirrorWindowInPrimary->getInfo(), *firstWindowInPrimary->getInfo(),
+              *secondWindowInPrimary->getInfo()},
+             {},
+             0,
+             0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -4693,14 +5397,14 @@
     secondWindowInPrimary->consumeMotionDown();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
     secondWindowInPrimary->consumeMotionMove();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
@@ -4728,14 +5432,18 @@
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
 
     // Update window info, let it find window handle of second display first.
-    mDispatcher->setInputWindows(
-            {{SECOND_DISPLAY_ID, {firstWindowInSecondary, secondWindowInSecondary}},
-             {ADISPLAY_ID_DEFAULT,
-              {mirrorWindowInPrimary, firstWindowInPrimary, secondWindowInPrimary}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindowInSecondary->getInfo(), *secondWindowInSecondary->getInfo(),
+              *mirrorWindowInPrimary->getInfo(), *firstWindowInPrimary->getInfo(),
+              *secondWindowInPrimary->getInfo()},
+             {},
+             0,
+             0});
 
     // Touch on second display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                               {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
@@ -4749,14 +5457,14 @@
     secondWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 SECOND_DISPLAY_ID, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
     secondWindowInPrimary->consumeMotionMove(SECOND_DISPLAY_ID);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50}))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
     secondWindowInPrimary->consumeMotionUp(SECOND_DISPLAY_ID);
@@ -4768,7 +5476,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -4790,7 +5498,7 @@
 
     window->setDisableUserActivity(true);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -4811,7 +5519,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -4832,7 +5540,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -4854,7 +5562,7 @@
 
     window->setDisableUserActivity(true);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -4874,10 +5582,10 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -4894,7 +5602,7 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
     mDispatcher->waitForIdle();
@@ -4908,7 +5616,7 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Send key
     mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
@@ -4935,7 +5643,8 @@
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
 
     PointF pointInFirst = {300, 200};
     PointF pointInSecond = {300, 600};
@@ -4979,7 +5688,7 @@
 
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
     graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
@@ -4989,74 +5698,6 @@
     mDispatcher->waitForIdle();
 }
 
-class FakeMonitorReceiver {
-public:
-    FakeMonitorReceiver(const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
-                        int32_t displayId) {
-        base::Result<std::unique_ptr<InputChannel>> channel =
-                dispatcher->createInputMonitor(displayId, name, MONITOR_PID);
-        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-    }
-
-    sp<IBinder> getToken() { return mInputReceiver->getToken(); }
-
-    void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
-                                     expectedFlags);
-    }
-
-    std::optional<int32_t> receiveEvent() { return mInputReceiver->receiveEvent(); }
-
-    void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); }
-
-    void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                     expectedDisplayId, expectedFlags);
-    }
-
-    void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
-                                     expectedDisplayId, expectedFlags);
-    }
-
-    void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
-                                     expectedDisplayId, expectedFlags);
-    }
-
-    void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeMotionEvent(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
-                      WithDisplayId(expectedDisplayId),
-                      WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
-    }
-
-    void consumeMotionPointerDown(int32_t pointerIdx) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
-                                     /*expectedFlags=*/0);
-    }
-
-    MotionEvent* consumeMotion() {
-        InputEvent* event = mInputReceiver->consume();
-        if (!event) {
-            ADD_FAILURE() << "No event was produced";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << "Expected MotionEvent, got " << *event;
-            return nullptr;
-        }
-        return static_cast<MotionEvent*>(event);
-    }
-
-    void assertNoEvents() { mInputReceiver->assertNoEvents(); }
-
-private:
-    std::unique_ptr<FakeInputReceiver> mInputReceiver;
-};
-
 using InputDispatcherMonitorTest = InputDispatcherTest;
 
 /**
@@ -5072,11 +5713,11 @@
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -5085,7 +5726,7 @@
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -5093,14 +5734,14 @@
     monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
 
     // Now the foreground window goes away
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
     window->consumeMotionCancel();
     monitor.assertNoEvents(); // Global monitor does not get a cancel yet
 
     // If more events come in, there will be no more foreground window to send them to. This will
     // cause a cancel for the monitor, as well.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {120, 200}))
             << "Injection should fail because the window was removed";
     window->assertNoEvents();
@@ -5112,27 +5753,27 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -5142,7 +5783,7 @@
     EXPECT_NE(OK, mDispatcher->pilferPointers(monitor.getToken()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -5154,14 +5795,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     window->setWindowOffset(20, 40);
     window->setWindowTransform(0, 1, -1, 0);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     MotionEvent* event = monitor.consumeMotion();
@@ -5171,10 +5812,10 @@
 
 TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Injection should fail if there is a monitor, but no touchable window";
     monitor.assertNoEvents();
 }
@@ -5184,7 +5825,7 @@
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
@@ -5221,13 +5862,13 @@
     window->setFocusable(true);
 
     SCOPED_TRACE("Check default value of touch mode");
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     SCOPED_TRACE("Remove the window to trigger focus loss");
     window->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/true);
 
     SCOPED_TRACE("Disable touch mode");
@@ -5235,13 +5876,13 @@
                                 /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
     window->consumeTouchModeEvent(false);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/false);
 
     SCOPED_TRACE("Remove the window to trigger focus loss");
     window->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/false);
 
     SCOPED_TRACE("Enable touch mode again");
@@ -5249,7 +5890,7 @@
                                 /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
     window->consumeTouchModeEvent(true);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
@@ -5264,7 +5905,7 @@
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
@@ -5272,7 +5913,7 @@
     const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
     mDispatcher->notifyKey(keyArgs);
 
-    InputEvent* event = window->consume();
+    KeyEvent* event = window->consumeKey();
     ASSERT_NE(event, nullptr);
 
     std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
@@ -5316,7 +5957,7 @@
                                ADISPLAY_ID_DEFAULT);
     mDispatcher->notifyMotion(motionArgs);
 
-    InputEvent* event = window->consume();
+    MotionEvent* event = window->consumeMotion();
     ASSERT_NE(event, nullptr);
 
     std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
@@ -5410,11 +6051,12 @@
     // Top window is also focusable but is not granted focus.
     windowTop->setFocusable(true);
     windowSecond->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     setFocusedWindow(windowSecond);
 
     windowSecond->consumeFocusEvent(true);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Focused window should receive event.
@@ -5431,12 +6073,11 @@
     window->setFocusable(true);
     // Release channel for window is no longer valid.
     window->releaseChannel();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     // Test inject a key down, should timeout.
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
-            << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+    ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
 
     // window channel is invalid, so it should not receive any input event.
     window->assertNoEvents();
@@ -5449,12 +6090,11 @@
     window->setFocusable(false);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     // Test inject a key down, should timeout.
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
-            << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+    ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
 
     // window is not focusable, so it should not receive any input event.
     window->assertNoEvents();
@@ -5470,16 +6110,18 @@
 
     windowTop->setFocusable(true);
     windowSecond->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     setFocusedWindow(windowTop);
     windowTop->consumeFocusEvent(true);
 
     windowTop->editInfo()->focusTransferTarget = windowSecond->getToken();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     windowSecond->consumeFocusEvent(true);
     windowTop->consumeFocusEvent(false);
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Focused window should receive event.
@@ -5497,11 +6139,12 @@
     windowTop->setFocusable(true);
     windowSecond->setFocusable(false);
     windowTop->editInfo()->focusTransferTarget = windowSecond->getToken();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     setFocusedWindow(windowTop);
     windowTop->consumeFocusEvent(true);
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Event should be dropped.
@@ -5521,7 +6164,8 @@
     window->setFocusable(true);
     previousFocusedWindow->setFocusable(true);
     window->setVisible(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, previousFocusedWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*window->getInfo(), *previousFocusedWindow->getInfo()}, {}, 0, 0});
     setFocusedWindow(previousFocusedWindow);
     previousFocusedWindow->consumeFocusEvent(true);
 
@@ -5531,15 +6175,15 @@
 
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                        InputEventInjectionSync::NONE));
+              injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
 
     // Window does not get focus event or key down.
     window->assertNoEvents();
 
     // Window becomes visible.
     window->setVisible(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Window receives focus event.
     window->consumeFocusEvent(true);
@@ -5555,7 +6199,7 @@
 
     // window is granted focus.
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(true);
 
@@ -5607,8 +6251,8 @@
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
     slipperyExitWindow->setFrame(Rect(0, 0, 100, 100));
 
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0});
 
     // Use notifyMotion instead of injecting to avoid dealing with injection permissions
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -5616,8 +6260,8 @@
                                                  {{50, 50}}));
     slipperyExitWindow->consumeMotionDown();
     slipperyExitWindow->setFrame(Rect(70, 70, 100, 100));
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
@@ -5649,8 +6293,8 @@
     rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100));
     rightDropTouchesWindow->setDropInput(true);
 
-    mDispatcher->setInputWindows(
-            {{ADISPLAY_ID_DEFAULT, {leftSlipperyWindow, rightDropTouchesWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*leftSlipperyWindow->getInfo(), *rightDropTouchesWindow->getInfo()}, {}, 0, 0});
 
     // Start touch in the left window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
@@ -5669,6 +6313,53 @@
     rightDropTouchesWindow->assertNoEvents();
 }
 
+/**
+ * A single window is on screen first. Touch is injected into that window. Next, a second window
+ * appears. Since the first window is slippery, touch will move from the first window to the second.
+ */
+TEST_F(InputDispatcherTest, InjectedTouchSlips) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> originalWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Original", ADISPLAY_ID_DEFAULT);
+    originalWindow->setFrame(Rect(0, 0, 200, 200));
+    originalWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> appearingWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Appearing", ADISPLAY_ID_DEFAULT);
+    appearingWindow->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*originalWindow->getInfo()}, {}, 0, 0});
+
+    // Touch down on the original window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+                                        .build()));
+    originalWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Now, a new window appears. This could be, for example, a notification shade that appears
+    // after user starts to drag down on the launcher window.
+    mDispatcher->onWindowInfosChanged(
+            {{*appearingWindow->getInfo(), *originalWindow->getInfo()}, {}, 0, 0});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(110))
+                                        .build()));
+    originalWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    appearingWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+                                        .build()));
+    appearingWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    originalWindow->assertNoEvents();
+    appearingWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) {
     using Uid = gui::Uid;
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -5690,7 +6381,8 @@
     rightWindow->setFrame(Rect(100, 0, 200, 100));
     rightWindow->setOwnerInfo(gui::Pid{3}, Uid{103});
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightSpy, rightWindow, leftWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*rightSpy->getInfo(), *rightWindow->getInfo(), *leftWindow->getInfo()}, {}, 0, 0});
 
     // Touch in the left window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
@@ -5755,7 +6447,7 @@
     window->setFrame(Rect(0, 0, 100, 100));
     window->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true));
 
@@ -5782,10 +6474,9 @@
 
     virtual void SetUp() override {
         mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
-        mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
         mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
-        mDispatcher->requestRefreshConfiguration();
-        mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
         ASSERT_EQ(OK, mDispatcher->start());
 
         setUpWindow();
@@ -5796,7 +6487,7 @@
         mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
 
         mWindow->setFocusable(true);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
         mWindow->consumeFocusEvent(true);
     }
@@ -5813,15 +6504,8 @@
 
     void expectKeyRepeatOnce(int32_t repeatCount) {
         SCOPED_TRACE(StringPrintf("Checking event with repeat count %" PRId32, repeatCount));
-        InputEvent* repeatEvent = mWindow->consume();
-        ASSERT_NE(nullptr, repeatEvent);
-
-        ASSERT_EQ(InputEventType::KEY, repeatEvent->getType());
-
-        KeyEvent* repeatKeyEvent = static_cast<KeyEvent*>(repeatEvent);
-        uint32_t eventAction = repeatKeyEvent->getAction();
-        EXPECT_EQ(AKEY_EVENT_ACTION_DOWN, eventAction);
-        EXPECT_EQ(repeatCount, repeatKeyEvent->getRepeatCount());
+        mWindow->consumeKeyEvent(
+                AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithRepeatCount(repeatCount)));
     }
 
     void sendAndConsumeKeyUp(int32_t deviceId) {
@@ -5901,7 +6585,7 @@
     GTEST_SKIP() << "Flaky test (b/270393106)";
     sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        InputEvent* repeatEvent = mWindow->consume();
+        KeyEvent* repeatEvent = mWindow->consumeKey();
         ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
         EXPECT_EQ(IdGenerator::Source::INPUT_DISPATCHER,
                   IdGenerator::getSource(repeatEvent->getId()));
@@ -5914,7 +6598,7 @@
 
     std::unordered_set<int32_t> idSet;
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        InputEvent* repeatEvent = mWindow->consume();
+        KeyEvent* repeatEvent = mWindow->consumeKey();
         ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
         int32_t id = repeatEvent->getId();
         EXPECT_EQ(idSet.end(), idSet.find(id));
@@ -5935,7 +6619,8 @@
         // Set focus window for primary display, but focused display would be second one.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1);
         windowInPrimary->setFocusable(true);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowInPrimary}}});
+        mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
+
         setFocusedWindow(windowInPrimary);
         windowInPrimary->consumeFocusEvent(true);
 
@@ -5948,7 +6633,8 @@
         // Set focus window for second display.
         mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2);
         windowInSecondary->setFocusable(true);
-        mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*windowInPrimary->getInfo(), *windowInSecondary->getInfo()}, {}, 0, 0});
         setFocusedWindow(windowInSecondary);
         windowInSecondary->consumeFocusEvent(true);
     }
@@ -5972,14 +6658,14 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch) {
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     windowInSecondary->assertNoEvents();
 
     // Test touch down on second display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
@@ -5988,27 +6674,26 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) {
     // Test inject a key down with display id specified.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDownNoRepeat(mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
     windowInSecondary->assertNoEvents();
 
     // Test inject a key down without display id specified.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
 
     // Remove all windows in secondary display.
-    mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}});
+    mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
     // Old focus should receive a cancel event.
     windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
                                     AKEY_EVENT_FLAG_CANCELED);
 
     // Test inject a key down, should timeout because of no target window.
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDownNoRepeat(mDispatcher))
-            << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+    ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
     windowInPrimary->assertNoEvents();
     windowInSecondary->consumeFocusEvent(false);
     windowInSecondary->assertNoEvents();
@@ -6017,13 +6702,13 @@
 // Test per-display input monitors for motion event.
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
-            FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+            FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -6032,7 +6717,7 @@
 
     // Test touch down on second display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
@@ -6041,7 +6726,7 @@
 
     // Lift up the touch from the second display
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID);
     monitorInSecondary.consumeMotionUp(SECOND_DISPLAY_ID);
@@ -6050,7 +6735,7 @@
     // If specific a display, it will dispatch to the focused window of particular display,
     // or it will dispatch to the focused window of focused display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
@@ -6062,12 +6747,12 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
     // Input monitor per display.
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
-            FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+            FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test inject a key down.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
@@ -6079,13 +6764,19 @@
     sp<FakeWindowHandle> secondWindowInPrimary =
             sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
     secondWindowInPrimary->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowInPrimary, secondWindowInPrimary}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*windowInPrimary->getInfo(), *secondWindowInPrimary->getInfo(),
+              *windowInSecondary->getInfo()},
+             {},
+             0,
+             0});
     setFocusedWindow(secondWindowInPrimary);
     windowInPrimary->consumeFocusEvent(false);
     secondWindowInPrimary->consumeFocusEvent(true);
 
     // Test inject a key down.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     windowInSecondary->assertNoEvents();
@@ -6094,20 +6785,20 @@
 
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) {
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
-            FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+            FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
     // Test touch down on second display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
     monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID);
@@ -6121,14 +6812,14 @@
 
     // Test inject a move motion event, no window/monitor should receive the event.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::FAILED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 SECOND_DISPLAY_ID, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::FAILED";
     windowInSecondary->assertNoEvents();
@@ -6176,36 +6867,36 @@
 // Test InputFilter for MotionEvent
 TEST_F(InputFilterTest, MotionEvent_InputFilter) {
     // Since the InputFilter is disabled by default, check if touch events aren't filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ false);
-    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ false);
+    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
 
     // Enable InputFilter
     mDispatcher->setInputFilterEnabled(true);
     // Test touch on both primary and second display, and check if both events are filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ true);
-    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ true);
+    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true);
+    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true);
 
     // Disable InputFilter
     mDispatcher->setInputFilterEnabled(false);
     // Test touch on both primary and second display, and check if both events aren't filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ false);
-    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ false);
+    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
 }
 
 // Test InputFilter for KeyEvent
 TEST_F(InputFilterTest, KeyEvent_InputFilter) {
     // Since the InputFilter is disabled by default, check if key event aren't filtered.
-    testNotifyKey(/*expectToBeFiltered*/ false);
+    testNotifyKey(/*expectToBeFiltered=*/false);
 
     // Enable InputFilter
     mDispatcher->setInputFilterEnabled(true);
     // Send a key event, and check if it is filtered.
-    testNotifyKey(/*expectToBeFiltered*/ true);
+    testNotifyKey(/*expectToBeFiltered=*/true);
 
     // Disable InputFilter
     mDispatcher->setInputFilterEnabled(false);
     // Send a key event, and check if it isn't filtered.
-    testNotifyKey(/*expectToBeFiltered*/ false);
+    testNotifyKey(/*expectToBeFiltered=*/false);
 }
 
 // Ensure that MotionEvents sent to the InputFilter through InputListener are converted to the
@@ -6228,8 +6919,8 @@
     mDispatcher->setInputFilterEnabled(true);
 
     // Ensure the correct transforms are used for the displays.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ true, firstDisplayTransform);
-    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ true, secondDisplayTransform);
+    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true, firstDisplayTransform);
+    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true, secondDisplayTransform);
 }
 
 class InputFilterInjectionPolicyTest : public InputDispatcherTest {
@@ -6251,7 +6942,7 @@
 
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
         mWindow->setFocusable(true);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
         mWindow->consumeFocusEvent(true);
     }
@@ -6268,15 +6959,10 @@
                 POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                   mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
-                                                InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
+                                                InputEventInjectionSync::WAIT_FOR_RESULT, 100ms,
                                                 policyFlags | additionalPolicyFlags));
 
-        InputEvent* received = mWindow->consume();
-        ASSERT_NE(nullptr, received);
-        ASSERT_EQ(resolvedDeviceId, received->getDeviceId());
-        ASSERT_EQ(received->getType(), InputEventType::KEY);
-        KeyEvent& keyEvent = static_cast<KeyEvent&>(*received);
-        ASSERT_EQ(flags, keyEvent.getFlags());
+        mWindow->consumeKeyEvent(AllOf(WithDeviceId(resolvedDeviceId), WithFlags(flags)));
     }
 
     void testInjectedMotion(int32_t policyFlags, int32_t injectedDeviceId, int32_t resolvedDeviceId,
@@ -6298,20 +6984,15 @@
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                          AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, eventTime,
                          eventTime,
-                         /*pointerCount*/ 1, pointerProperties, pointerCoords);
+                         /*pointerCount=*/1, pointerProperties, pointerCoords);
 
         const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                   mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
-                                                InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
+                                                InputEventInjectionSync::WAIT_FOR_RESULT, 100ms,
                                                 policyFlags | additionalPolicyFlags));
 
-        InputEvent* received = mWindow->consume();
-        ASSERT_NE(nullptr, received);
-        ASSERT_EQ(resolvedDeviceId, received->getDeviceId());
-        ASSERT_EQ(received->getType(), InputEventType::MOTION);
-        MotionEvent& motionEvent = static_cast<MotionEvent&>(*received);
-        ASSERT_EQ(flags, motionEvent.getFlags());
+        mWindow->consumeMotionEvent(AllOf(WithFlags(flags), WithDeviceId(resolvedDeviceId)));
     }
 
 private:
@@ -6363,7 +7044,8 @@
         mFocusedWindow->setFocusable(true);
 
         // Expect one focus window exist in display.
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mFocusedWindow);
         mFocusedWindow->consumeFocusEvent(true);
     }
@@ -6386,7 +7068,7 @@
 // the onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Success) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {20, 20}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mUnfocusedWindow->consumeMotionDown();
@@ -6400,7 +7082,8 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPointerSource) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT, {20, 20}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT,
+                               {20, 20}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
 
@@ -6412,7 +7095,7 @@
 // have focus. Ensure no window received the onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDownNoRepeat(mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
 
@@ -6425,7 +7108,7 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                FOCUSED_WINDOW_TOUCH_POINT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
@@ -6443,7 +7126,7 @@
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(20).y(20))
                     .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)
                     .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, event))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
@@ -6469,7 +7152,7 @@
                                               ADISPLAY_ID_DEFAULT, mWindow1->getToken());
         mWindow2->setFrame(Rect(100, 100, 200, 200));
 
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}});
+        mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     }
 
 protected:
@@ -6485,28 +7168,24 @@
     void consumeMotionEvent(const sp<FakeWindowHandle>& window, int32_t expectedAction,
                             const std::vector<PointF>& points) {
         const std::string name = window->getName();
-        InputEvent* event = window->consume();
+        MotionEvent* motionEvent = window->consumeMotion();
 
-        ASSERT_NE(nullptr, event) << name.c_str()
-                                  << ": consumer should have returned non-NULL event.";
+        ASSERT_NE(nullptr, motionEvent)
+                << name.c_str() << ": consumer should have returned non-NULL event.";
 
-        ASSERT_EQ(InputEventType::MOTION, event->getType())
-                << name.c_str() << ": expected MotionEvent, got " << *event;
-
-        const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-        assertMotionAction(expectedAction, motionEvent.getAction());
-        ASSERT_EQ(points.size(), motionEvent.getPointerCount());
+        ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
+        ASSERT_EQ(points.size(), motionEvent->getPointerCount());
 
         for (size_t i = 0; i < points.size(); i++) {
             float expectedX = points[i].x;
             float expectedY = points[i].y;
 
-            EXPECT_EQ(expectedX, motionEvent.getX(i))
+            EXPECT_EQ(expectedX, motionEvent->getX(i))
                     << "expected " << expectedX << " for x[" << i << "] coord of " << name.c_str()
-                    << ", got " << motionEvent.getX(i);
-            EXPECT_EQ(expectedY, motionEvent.getY(i))
+                    << ", got " << motionEvent->getX(i);
+            EXPECT_EQ(expectedY, motionEvent->getY(i))
                     << "expected " << expectedY << " for y[" << i << "] coord of " << name.c_str()
-                    << ", got " << motionEvent.getY(i);
+                    << ", got " << motionEvent->getY(i);
         }
     }
 
@@ -6538,6 +7217,7 @@
 TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentTransform) {
     // Set scale value for window2
     mWindow2->setWindowScale(0.5f, 0.5f);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
 
     // Touch Window 1
     PointF touchedPoint = {10, 10};
@@ -6554,12 +7234,14 @@
 
     // Update the transform so rotation is set
     mWindow2->setWindowTransform(0, -1, 1, 0);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint);
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform) {
     mWindow2->setWindowScale(0.5f, 0.5f);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
 
     // Touch Window 1
     std::vector<PointF> touchedPoints = {PointF{10, 10}};
@@ -6577,12 +7259,14 @@
 
     // Update the transform so rotation is set for Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
     touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) {
     mWindow2->setWindowScale(0.5f, 0.5f);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
 
     // Touch Window 1
     std::vector<PointF> touchedPoints = {PointF{10, 10}};
@@ -6608,6 +7292,7 @@
 
     // Touch Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
     touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
@@ -6621,6 +7306,7 @@
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithScale) {
     mWindow1->setWindowScale(0.5f, 0.5f);
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
 
     // Touch Window 1
     std::vector<PointF> touchedPoints = {PointF{10, 10}};
@@ -6647,7 +7333,7 @@
  */
 TEST_F(InputDispatcherMultiWindowSameTokenTests, TouchDoesNotSlipEvenIfSlippery) {
     mWindow1->setSlippery(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
 
     // Touch down in window 1
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
@@ -6669,18 +7355,18 @@
  * that the pointer is hovering over may have a different transform.
  */
 TEST_F(InputDispatcherMultiWindowSameTokenTests, HoverIntoClone) {
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
 
     // Start hover in window 1
-    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{50, 50}}));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
     consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER,
                        {getPointInWindow(mWindow1->getInfo(), PointF{50, 50})});
-
     // Move hover to window 2.
-    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{150, 150}}));
-
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
+                                      .build());
     consumeMotionEvent(mWindow1, ACTION_HOVER_EXIT, {{50, 50}});
     consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER,
                        {getPointInWindow(mWindow2->getInfo(), PointF{150, 150})});
@@ -6691,17 +7377,17 @@
         InputDispatcherTest::SetUp();
 
         mApplication = std::make_shared<FakeApplicationHandle>();
-        mApplication->setDispatchingTimeout(20ms);
+        mApplication->setDispatchingTimeout(100ms);
         mWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "TestWindow",
                                              ADISPLAY_ID_DEFAULT);
         mWindow->setFrame(Rect(0, 0, 30, 30));
-        mWindow->setDispatchingTimeout(30ms);
+        mWindow->setDispatchingTimeout(100ms);
         mWindow->setFocusable(true);
 
         // Set focused application.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
 
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
         mWindow->consumeFocusEvent(true);
     }
@@ -6712,16 +7398,17 @@
     }
 
 protected:
+    static constexpr std::chrono::duration SPY_TIMEOUT = 200ms;
     std::shared_ptr<FakeApplicationHandle> mApplication;
     sp<FakeWindowHandle> mWindow;
     static constexpr PointF WINDOW_LOCATION = {20, 20};
 
     void tapOnWindow() {
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                    WINDOW_LOCATION));
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                  WINDOW_LOCATION));
     }
 
@@ -6731,8 +7418,8 @@
         spy->setTrustedOverlay(true);
         spy->setFocusable(false);
         spy->setSpy(true);
-        spy->setDispatchingTimeout(30ms);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, mWindow}}});
+        spy->setDispatchingTimeout(SPY_TIMEOUT);
+        mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *mWindow->getInfo()}, {}, 0, 0});
         return spy;
     }
 };
@@ -6748,7 +7435,7 @@
 
 // Send a regular key and respond, which should not cause an ANR.
 TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) {
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(mDispatcher));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -6756,12 +7443,12 @@
 
 TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) {
     mWindow->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
 
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, CONSUME_TIMEOUT_EVENT_EXPECTED,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // Key will not go to window because we have no focused window.
@@ -6780,7 +7467,7 @@
 // So InputDispatcher will enqueue ACTION_CANCEL event as well.
 TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                WINDOW_LOCATION));
 
     std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
@@ -6798,7 +7485,7 @@
 // Send a key to the app and have the app not respond right away.
 TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) {
     // Inject a key, and don't respond - expect that ANR is called.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(mDispatcher));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
     std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -6809,12 +7496,12 @@
 // We have a focused application, but no focused window
 TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) {
     mWindow->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
 
     // taps on the window work as normal
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                WINDOW_LOCATION));
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
     mDispatcher->waitForIdle();
@@ -6824,8 +7511,8 @@
     // We specify the injection timeout to be smaller than the application timeout, to ensure that
     // injection times out (instead of failing).
     const InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
@@ -6838,7 +7525,7 @@
  */
 TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) {
     mWindow->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
 
     KeyEvent event;
@@ -6847,7 +7534,7 @@
 
     // Define a valid key down event that is stale (too old).
     event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A,
+                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A,
                      AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime);
 
     const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
@@ -6867,20 +7554,29 @@
 // We have a focused application, but no focused window
 // Make sure that we don't notify policy twice about the same ANR.
 TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
+    const std::chrono::duration appTimeout = 400ms;
+    mApplication->setDispatchingTimeout(appTimeout);
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+
     mWindow->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
 
     // Once a focused event arrives, we get an ANR for this application
     // We specify the injection timeout to be smaller than the application timeout, to ensure that
     // injection times out (instead of failing).
+    const std::chrono::duration eventInjectionTimeout = 100ms;
+    ASSERT_LT(eventInjectionTimeout, appTimeout);
     const InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false);
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
-    const std::chrono::duration appTimeout =
-            mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(appTimeout, mApplication);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout,
+                      /*allowKeyRepeat=*/false);
+    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result)
+            << "result=" << ftl::enum_string(result);
+    // We already waited for 'eventInjectionTimeout`, because the countdown started when the event
+    // was first injected. So now we have (appTimeout - eventInjectionTimeout) left to wait.
+    std::chrono::duration remainingWaitTime = appTimeout - eventInjectionTimeout;
+    mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(remainingWaitTime, mApplication);
 
     std::this_thread::sleep_for(appTimeout);
     // ANR should not be raised again. It is up to policy to do that if it desires.
@@ -6894,20 +7590,17 @@
 // We have a focused application, but no focused window
 TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) {
     mWindow->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
 
     // Once a focused event arrives, we get an ANR for this application
-    const InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
+    ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
 
     const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
 
     // Future focused events get dropped right away
-    ASSERT_EQ(InputEventInjectionResult::FAILED, injectKeyDown(mDispatcher));
+    ASSERT_EQ(InputEventInjectionResult::FAILED, injectKeyDown(*mDispatcher));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mWindow->assertNoEvents();
 }
@@ -6923,14 +7616,14 @@
  */
 TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+    injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                       ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
                       {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                        AMOTION_EVENT_INVALID_CURSOR_POSITION},
                       500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime);
 
     // Now send ACTION_UP, with identical timestamp
-    injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+    injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
                       ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
                       {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                        AMOTION_EVENT_INVALID_CURSOR_POSITION},
@@ -6947,7 +7640,7 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                WINDOW_LOCATION));
     mWindow->consumeMotionDown();
 
@@ -6969,9 +7662,9 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT));
+              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT));
     mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher, ADISPLAY_ID_DEFAULT));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher, ADISPLAY_ID_DEFAULT));
 
     // Stuck on the ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -7016,19 +7709,20 @@
 }
 
 TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) {
-    mDispatcher->setMonitorDispatchingTimeoutForTest(30ms);
+    mDispatcher->setMonitorDispatchingTimeoutForTest(SPY_TIMEOUT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                WINDOW_LOCATION));
 
     mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     const std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
     ASSERT_TRUE(consumeSeq);
 
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(30ms, monitor.getToken(), MONITOR_PID);
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(SPY_TIMEOUT, monitor.getToken(),
+                                                         MONITOR_PID);
 
     monitor.finishEvent(*consumeSeq);
     monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
@@ -7070,7 +7764,7 @@
 // it.
 TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                WINDOW_LOCATION));
 
     const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -7090,19 +7784,19 @@
 
 /**
  * If a window is processing a motion event, and then a key event comes in, the key event should
- * not to to the focused window until the motion is processed.
+ * not get delivered to the focused window until the motion is processed.
  *
  * Warning!!!
  * This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
  * and the injection timeout that we specify when injecting the key.
- * We must have the injection timeout (10ms) be smaller than
+ * We must have the injection timeout (100ms) be smaller than
  *  KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
  *
  * If that value changes, this test should also change.
  */
 TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) {
     mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
 
     tapOnWindow();
     std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
@@ -7115,13 +7809,15 @@
     // we will receive INJECTION_TIMED_OUT as the result.
 
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::WAIT_FOR_RESULT, 100ms);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     // Key will not be sent to the window, yet, because the window is still processing events
     // and the key remains pending, waiting for the touch events to be processed
-    std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
-    ASSERT_FALSE(keySequenceNum);
+    // Make sure that `assertNoEvents` doesn't wait too long, because it could cause an ANR.
+    // Rely here on the fact that it uses CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
+    static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
+    mWindow->assertNoEvents();
 
     std::this_thread::sleep_for(500ms);
     // if we wait long enough though, dispatcher will give up, and still send the key
@@ -7140,7 +7836,7 @@
 TEST_F(InputDispatcherSingleWindowAnr,
        PendingKey_IsDroppedWhileMotionIsProcessedAndNewTouchComesIn) {
     mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
 
     tapOnWindow();
     std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
@@ -7150,11 +7846,13 @@
     // Don't finish the events yet, and send a key
     // Injection is async, so it will succeed
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                        InputEventInjectionSync::NONE));
+              injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
     // At this point, key is still pending, and should not be sent to the application yet.
-    std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
-    ASSERT_FALSE(keySequenceNum);
+    // Make sure the `assertNoEvents` check doesn't take too long. It uses
+    // CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
+    static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
+    mWindow->assertNoEvents();
 
     // Now tap down again. It should cause the pending key to go to the focused window right away.
     tapOnWindow();
@@ -7221,7 +7919,7 @@
         InputDispatcherTest::SetUp();
 
         mApplication = std::make_shared<FakeApplicationHandle>();
-        mApplication->setDispatchingTimeout(10ms);
+        mApplication->setDispatchingTimeout(100ms);
         mUnfocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Unfocused",
                                                       ADISPLAY_ID_DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
@@ -7230,7 +7928,7 @@
 
         mFocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Focused",
                                                     ADISPLAY_ID_DEFAULT);
-        mFocusedWindow->setDispatchingTimeout(30ms);
+        mFocusedWindow->setDispatchingTimeout(100ms);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
 
         // Set focused application.
@@ -7238,7 +7936,8 @@
         mFocusedWindow->setFocusable(true);
 
         // Expect one focus window exist in display.
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mFocusedWindow);
         mFocusedWindow->consumeFocusEvent(true);
     }
@@ -7265,10 +7964,10 @@
 private:
     void tap(const PointF& location) {
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                    location));
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                  location));
     }
 };
@@ -7277,10 +7976,23 @@
 // should be ANR'd first.
 TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
     mFocusedWindow->consumeMotionDown();
+    mFocusedWindow->consumeMotionUp();
     mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
                                    ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
@@ -7288,17 +8000,20 @@
     mFakePolicy->assertNotifyAnrWasNotCalled();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
     std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent();
     ASSERT_TRUE(unfocusedSequenceNum);
 
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
-    // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event
-    // sequence to make it consistent
-    mFocusedWindow->consumeMotionCancel();
+
     mUnfocusedWindow->finishEvent(*unfocusedSequenceNum);
     mFocusedWindow->consumeMotionDown();
     // This cancel is generated because the connection was unresponsive
@@ -7316,20 +8031,22 @@
 // But we should receive ANR for both.
 TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout) {
     // Set the timeout for unfocused window to match the focused window
-    mUnfocusedWindow->setDispatchingTimeout(10ms);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+    mUnfocusedWindow->setDispatchingTimeout(
+            mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
+    mDispatcher->onWindowInfosChanged(
+            {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
 
     tapOnFocusedWindow();
     // we should have ACTION_DOWN/ACTION_UP on focused window and ACTION_OUTSIDE on unfocused window
-    sp<IBinder> anrConnectionToken1, anrConnectionToken2;
-    ASSERT_NO_FATAL_FAILURE(anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms));
-    ASSERT_NO_FATAL_FAILURE(anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms));
-
     // We don't know which window will ANR first. But both of them should happen eventually.
-    ASSERT_TRUE(mFocusedWindow->getToken() == anrConnectionToken1 ||
-                mFocusedWindow->getToken() == anrConnectionToken2);
-    ASSERT_TRUE(mUnfocusedWindow->getToken() == anrConnectionToken1 ||
-                mUnfocusedWindow->getToken() == anrConnectionToken2);
+    std::array<sp<IBinder>, 2> anrConnectionTokens = {mFakePolicy->getUnresponsiveWindowToken(
+                                                              mFocusedWindow->getDispatchingTimeout(
+                                                                      DISPATCHING_TIMEOUT)),
+                                                      mFakePolicy->getUnresponsiveWindowToken(0ms)};
+
+    ASSERT_THAT(anrConnectionTokens,
+                ::testing::UnorderedElementsAre(testing::Eq(mFocusedWindow->getToken()),
+                                                testing::Eq(mUnfocusedWindow->getToken())));
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -7338,15 +8055,13 @@
     mFocusedWindow->consumeMotionUp();
     mUnfocusedWindow->consumeMotionOutside();
 
-    sp<IBinder> responsiveToken1, responsiveToken2;
-    ASSERT_NO_FATAL_FAILURE(responsiveToken1 = mFakePolicy->getResponsiveWindowToken());
-    ASSERT_NO_FATAL_FAILURE(responsiveToken2 = mFakePolicy->getResponsiveWindowToken());
+    std::array<sp<IBinder>, 2> responsiveTokens = {mFakePolicy->getResponsiveWindowToken(),
+                                                   mFakePolicy->getResponsiveWindowToken()};
 
     // Both applications should be marked as responsive, in any order
-    ASSERT_TRUE(mFocusedWindow->getToken() == responsiveToken1 ||
-                mFocusedWindow->getToken() == responsiveToken2);
-    ASSERT_TRUE(mUnfocusedWindow->getToken() == responsiveToken1 ||
-                mUnfocusedWindow->getToken() == responsiveToken2);
+    ASSERT_THAT(responsiveTokens,
+                ::testing::UnorderedElementsAre(testing::Eq(mFocusedWindow->getToken()),
+                                                testing::Eq(mUnfocusedWindow->getToken())));
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
 
@@ -7369,10 +8084,10 @@
     // Tap once again
     // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                FOCUSED_WINDOW_LOCATION));
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                              FOCUSED_WINDOW_LOCATION));
     // Unfocused window does not receive ACTION_OUTSIDE because the tapped window is not a
     // valid touch target
@@ -7393,7 +8108,7 @@
 // If you tap outside of all windows, there will not be ANR
 TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) {
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                LOCATION_OUTSIDE_ALL_WINDOWS));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -7402,10 +8117,11 @@
 // Since the focused window is paused, tapping on it should not produce any events
 TEST_F(InputDispatcherMultiWindowAnr, Window_CanBePaused) {
     mFocusedWindow->setPaused(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                FOCUSED_WINDOW_LOCATION));
 
     std::this_thread::sleep_for(mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
@@ -7419,13 +8135,13 @@
 
 /**
  * If a window is processing a motion event, and then a key event comes in, the key event should
- * not to to the focused window until the motion is processed.
+ * not get delivered to the focused window until the motion is processed.
  * If a different window becomes focused at this time, the key should go to that window instead.
  *
  * Warning!!!
  * This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
  * and the injection timeout that we specify when injecting the key.
- * We must have the injection timeout (10ms) be smaller than
+ * We must have the injection timeout (100ms) be smaller than
  *  KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
  *
  * If that value changes, this test should also change.
@@ -7433,7 +8149,8 @@
 TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) {
     // Set a long ANR timeout to prevent it from triggering
     mFocusedWindow->setDispatchingTimeout(2s);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
 
     tapOnUnfocusedWindow();
     std::optional<uint32_t> downSequenceNum = mUnfocusedWindow->receiveEvent();
@@ -7445,18 +8162,21 @@
     // window even if motions are still being processed.
 
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // Key will not be sent to the window, yet, because the window is still processing events
-    // and the key remains pending, waiting for the touch events to be processed
-    std::optional<uint32_t> keySequenceNum = mFocusedWindow->receiveEvent();
-    ASSERT_FALSE(keySequenceNum);
+    // and the key remains pending, waiting for the touch events to be processed.
+    // Make sure `assertNoEvents` doesn't take too long. It uses CONSUME_TIMEOUT_NO_EVENT_EXPECTED
+    // under the hood.
+    static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
+    mFocusedWindow->assertNoEvents();
 
     // Switch the focus to the "unfocused" window that we tapped. Expect the key to go there
     mFocusedWindow->setFocusable(false);
     mUnfocusedWindow->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
     setFocusedWindow(mUnfocusedWindow);
 
     // Focus events should precede the key events
@@ -7534,20 +8254,21 @@
 TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_NoAnr) {
     std::shared_ptr<FakeApplicationHandle> focusedApplication =
             std::make_shared<FakeApplicationHandle>();
-    focusedApplication->setDispatchingTimeout(60ms);
+    focusedApplication->setDispatchingTimeout(300ms);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, focusedApplication);
     // The application that owns 'mFocusedWindow' and 'mUnfocusedWindow' is not focused.
     mFocusedWindow->setFocusable(false);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
     mFocusedWindow->consumeFocusEvent(false);
 
     // Send a key. The ANR timer should start because there is no focused window.
     // 'focusedApplication' will get blamed if this timer completes.
     // Key will not be sent anywhere because we have no focused window. It will remain pending.
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
 
@@ -7558,7 +8279,7 @@
     // simply be added to the queue without 'shouldPruneInboundQueueLocked' returning 'true'.
     // For this test, it means that the key would get delivered to the window once it becomes
     // focused.
-    std::this_thread::sleep_for(10ms);
+    std::this_thread::sleep_for(100ms);
 
     // Touch unfocused window. This should force the pending key to get dropped.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
@@ -7569,7 +8290,8 @@
     // process (== drop) the key event, and by that time, ANR will be raised.
     // Set the focused window first.
     mFocusedWindow->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
     setFocusedWindow(mFocusedWindow);
     mFocusedWindow->consumeFocusEvent(true);
     // We do not call "setFocusedApplication" here, even though the newly focused window belongs
@@ -7606,7 +8328,8 @@
                                                    ADISPLAY_ID_DEFAULT);
         mBottomWindow->setFrame(Rect(0, 0, 100, 100));
 
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*mNoInputWindow->getInfo(), *mBottomWindow->getInfo()}, {}, 0, 0});
     }
 
 protected:
@@ -7641,7 +8364,8 @@
 
     mNoInputWindow->setNoInputChannel(true);
     mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mNoInputWindow->getInfo(), *mBottomWindow->getInfo()}, {}, 0, 0});
 
     PointF touchedPoint = {10, 10};
 
@@ -7668,7 +8392,7 @@
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
         mWindow->setFocusable(true);
         mMirror->setFocusable(true);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
     }
 };
 
@@ -7678,7 +8402,7 @@
 
     // window gets focused
     mWindow->consumeFocusEvent(true);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
 }
@@ -7690,20 +8414,20 @@
 
     // window gets focused
     mWindow->consumeFocusEvent(true);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
 
     mMirror->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
 
     // window loses focus since one of the windows associated with the token in not focusable
     mWindow->consumeFocusEvent(false);
 
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
     mWindow->assertNoEvents();
 }
@@ -7715,30 +8439,30 @@
 
     // window gets focused
     mWindow->consumeFocusEvent(true);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
 
     mMirror->setVisible(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
 
     mWindow->setVisible(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
 
     // window loses focus only after all windows associated with the token become invisible.
     mWindow->consumeFocusEvent(false);
 
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
     mWindow->assertNoEvents();
 }
@@ -7749,28 +8473,28 @@
 
     // window gets focused
     mWindow->consumeFocusEvent(true);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
 
     // single window is removed but the window token remains focused
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mMirror}}});
+    mDispatcher->onWindowInfosChanged({{*mMirror->getInfo()}, {}, 0, 0});
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
 
     // Both windows are removed
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
 
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
     mWindow->assertNoEvents();
 }
@@ -7780,16 +8504,16 @@
     // Request focus on an invisible mirror.
     mWindow->setVisible(false);
     mMirror->setVisible(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
     setFocusedWindow(mMirror);
 
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                        InputEventInjectionSync::NONE));
+              injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
 
     mMirror->setVisible(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
 
     // window gets focused
     mWindow->consumeFocusEvent(true);
@@ -7813,7 +8537,8 @@
         mSecondWindow->setFocusable(true);
 
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
 
         setFocusedWindow(mWindow);
         mWindow->consumeFocusEvent(true);
@@ -7944,6 +8669,47 @@
     mWindow->assertNoEvents();
 }
 
+/**
+ * One window. Hover mouse in the window, and then start capture. Make sure that the relative
+ * mouse movements don't affect the previous mouse hovering state.
+ * When pointer capture is enabled, the incoming events are always ACTION_MOVE (there are no
+ * HOVER_MOVE events).
+ */
+TEST_F(InputDispatcherPointerCaptureTests, MouseHoverAndPointerCapture) {
+    // Mouse hover on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER)));
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE)));
+
+    // Start pointer capture
+    requestAndVerifyPointerCapture(mWindow, true);
+
+    // Send some relative mouse movements and receive them in the window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(11))
+                                      .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithCoords(10, 11),
+                                      WithSource(AINPUT_SOURCE_MOUSE_RELATIVE)));
+
+    // Stop pointer capture
+    requestAndVerifyPointerCapture(mWindow, false);
+
+    // Continue hovering on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(105).y(115))
+                                      .build());
+    mWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+
+    mWindow->assertNoEvents();
+}
+
 class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
 protected:
     constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
@@ -8005,7 +8771,7 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithBlockUntrustedOcclusionMode_BlocksTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8016,7 +8782,7 @@
        WindowWithBlockUntrustedOcclusionModeWithOpacityBelowThreshold_BlocksTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.7f);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8027,7 +8793,7 @@
        WindowWithBlockUntrustedOcclusionMode_DoesNotReceiveTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8036,7 +8802,7 @@
 
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithAllowOcclusionMode_AllowsTouch) {
     const sp<FakeWindowHandle>& w = getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::ALLOW);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8047,7 +8813,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
     w->setFrame(Rect(0, 0, 50, 50));
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch({PointF{100, 100}});
 
@@ -8057,7 +8823,7 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowFromSameUid_AllowsTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(TOUCHED_APP_UID, "A", TouchOcclusionMode::BLOCK_UNTRUSTED);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8067,7 +8833,7 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_AllowsTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8077,7 +8843,7 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_DoesNotReceiveTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8094,7 +8860,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
     w->setWatchOutsideTouch(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8105,7 +8871,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
     w->setWatchOutsideTouch(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8116,7 +8882,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8127,7 +8893,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                MAXIMUM_OBSCURING_OPACITY);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8138,7 +8904,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_ABOVE_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8153,7 +8919,8 @@
     const sp<FakeWindowHandle>& w2 =
             getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*w1->getInfo(), *w2->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8168,7 +8935,8 @@
     const sp<FakeWindowHandle>& w2 =
             getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_FAR_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*w1->getInfo(), *w2->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8183,7 +8951,8 @@
     const sp<FakeWindowHandle>& wC =
             getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*wB->getInfo(), *wC->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8197,7 +8966,8 @@
     const sp<FakeWindowHandle>& wC =
             getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_ABOVE_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*wB->getInfo(), *wC->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8212,7 +8982,8 @@
     const sp<FakeWindowHandle>& wB =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_ABOVE_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wA, wB, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*wA->getInfo(), *wB->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8227,7 +8998,8 @@
     const sp<FakeWindowHandle>& wB =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wA, wB, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*wA->getInfo(), *wB->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8238,7 +9010,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_ABOVE_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8248,7 +9020,7 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, SelfWindowWithBlockUntrustedMode_AllowsTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::BLOCK_UNTRUSTED);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8260,7 +9032,7 @@
     mDispatcher->setMaximumObscuringOpacityForTouch(0.0f);
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.1f);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8271,7 +9043,7 @@
     mDispatcher->setMaximumObscuringOpacityForTouch(0.0f);
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.0f);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8284,7 +9056,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_ABOVE_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8299,7 +9071,8 @@
     const sp<FakeWindowHandle>& w2 =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*w1->getInfo(), *w2->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8319,7 +9092,8 @@
     const sp<FakeWindowHandle>& wC =
             getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
                                OPACITY_BELOW_THRESHOLD);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*wB->getInfo(), *wC->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8335,7 +9109,7 @@
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
     w->setApplicationToken(mTouchWindow->getApplicationToken());
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+    mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
 
     touch();
 
@@ -8369,40 +9143,44 @@
         mSpyWindow->setFrame(Rect(0, 0, 200, 100));
 
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mSpyWindow, mWindow, mSecondWindow}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()},
+                 {},
+                 0,
+                 0});
     }
 
     void injectDown(int fromSource = AINPUT_SOURCE_TOUCHSCREEN) {
         switch (fromSource) {
             case AINPUT_SOURCE_TOUCHSCREEN:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                          injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                          injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
                                            ADISPLAY_ID_DEFAULT, {50, 50}))
                         << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
                 break;
             case AINPUT_SOURCE_STYLUS:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                          injectMotionEvent(
-                                  mDispatcher,
-                                  MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_STYLUS)
-                                          .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
-                                          .pointer(PointerBuilder(0, ToolType::STYLUS)
-                                                           .x(50)
-                                                           .y(50))
-                                          .build()));
+                          injectMotionEvent(*mDispatcher,
+                                            MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                               AINPUT_SOURCE_STYLUS)
+                                                    .buttonState(
+                                                            AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                                    .pointer(PointerBuilder(0, ToolType::STYLUS)
+                                                                     .x(50)
+                                                                     .y(50))
+                                                    .build()));
                 break;
             case AINPUT_SOURCE_MOUSE:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                          injectMotionEvent(
-                                  mDispatcher,
-                                  MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
-                                          .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                          .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                  ToolType::MOUSE)
-                                                           .x(50)
-                                                           .y(50))
-                                          .build()));
+                          injectMotionEvent(*mDispatcher,
+                                            MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                               AINPUT_SOURCE_MOUSE)
+                                                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                                    .pointer(PointerBuilder(MOUSE_POINTER_ID,
+                                                                            ToolType::MOUSE)
+                                                                     .x(50)
+                                                                     .y(50))
+                                                    .build()));
                 break;
             default:
                 FAIL() << "Source " << fromSource << " doesn't support drag and drop";
@@ -8426,8 +9204,11 @@
         mDragWindow =
                 sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
         mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}});
-        mDispatcher->setInputWindows(
-                {{ADISPLAY_ID_DEFAULT, {mDragWindow, mSpyWindow, mWindow, mSecondWindow}}});
+        mDispatcher->onWindowInfosChanged({{*mDragWindow->getInfo(), *mSpyWindow->getInfo(),
+                                            *mWindow->getInfo(), *mSecondWindow->getInfo()},
+                                           {},
+                                           0,
+                                           0});
 
         // Transfer touch focus to the drag window
         bool transferred =
@@ -8446,7 +9227,7 @@
 
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8455,7 +9236,7 @@
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8464,7 +9245,7 @@
 
     // Move back to original window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8472,7 +9253,8 @@
     mSecondWindow->consumeDragEvent(true, -50, 50);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                             {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
     mWindow->assertNoEvents();
@@ -8492,7 +9274,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -8508,7 +9290,7 @@
 
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8517,7 +9299,7 @@
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8526,7 +9308,7 @@
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
@@ -8540,7 +9322,7 @@
 
     // Move on window and keep button pressed.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                         .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
@@ -8552,7 +9334,7 @@
 
     // Move to another window and release button, expect to drop item.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
@@ -8565,7 +9347,7 @@
 
     // nothing to the window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
@@ -8581,11 +9363,12 @@
 
     // Set second window invisible.
     mSecondWindow->setVisible(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mDragWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
 
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8594,7 +9377,7 @@
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8603,7 +9386,7 @@
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
@@ -8617,7 +9400,7 @@
     mWindow->setPreventSplitting(true);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -8630,7 +9413,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
@@ -8642,7 +9425,7 @@
 TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) {
     // First down on second window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -8657,7 +9440,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -8673,7 +9456,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
     mWindow->consumeDragEvent(false, 50, 50);
@@ -8687,7 +9470,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
     mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken());
@@ -8701,11 +9484,16 @@
     // Update window of second display.
     sp<FakeWindowHandle> windowInSecondary =
             sp<FakeWindowHandle>::make(mApp, mDispatcher, "D_2", SECOND_DISPLAY_ID);
-    mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
+              *mSecondWindow->getInfo(), *windowInSecondary->getInfo()},
+             {},
+             0,
+             0});
 
     // Let second display has a touch state.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
                                         .displayId(SECOND_DISPLAY_ID)
@@ -8714,11 +9502,16 @@
     windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
                                     SECOND_DISPLAY_ID, /*expectedFlag=*/0);
     // Update window again.
-    mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
+              *mSecondWindow->getInfo(), *windowInSecondary->getInfo()},
+             {},
+             0,
+             0});
 
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8727,7 +9520,7 @@
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -8736,7 +9529,7 @@
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
@@ -8749,11 +9542,10 @@
     startDrag(true, AINPUT_SOURCE_MOUSE);
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                ToolType::MOUSE)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
                                                          .x(50)
                                                          .y(50))
                                         .build()))
@@ -8764,11 +9556,10 @@
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
                                         .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                        .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                ToolType::MOUSE)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
                                                          .x(150)
                                                          .y(50))
                                         .build()))
@@ -8779,11 +9570,10 @@
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
-                                        .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                ToolType::MOUSE)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
                                                          .x(150)
                                                          .y(50))
                                         .build()))
@@ -8801,7 +9591,7 @@
 TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) {
     // Down on second window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -8816,7 +9606,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
@@ -8841,7 +9631,7 @@
 
     // Inject a simple gesture, ensure dispatcher not crashed
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                PointF{50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
@@ -8852,13 +9642,13 @@
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, moveEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, moveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionMove());
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                              {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionUp());
@@ -8873,7 +9663,7 @@
     window->setDropInput(true);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
@@ -8889,7 +9679,7 @@
 
     // With the flag cleared, the window should get input
     window->setDropInput(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
@@ -8916,7 +9706,8 @@
     window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
@@ -8932,7 +9723,8 @@
 
     // With the flag cleared, the window should get input
     window->setDropInputIfObscured(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
@@ -8959,7 +9751,8 @@
     window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
@@ -8974,7 +9767,8 @@
     window->assertNoEvents();
 
     // When the window is no longer obscured because it went on top, it should get input
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, obscuringWindow}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*window->getInfo(), *obscuringWindow->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
@@ -9010,8 +9804,11 @@
         mThirdWindow->setFocusable(true);
 
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}},
-                                      {SECOND_DISPLAY_ID, {mThirdWindow}}});
+        mDispatcher->onWindowInfosChanged(
+                {{*mWindow->getInfo(), *mSecondWindow->getInfo(), *mThirdWindow->getInfo()},
+                 {},
+                 0,
+                 0});
         mThirdWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
         mWindow->consumeFocusEvent(true);
 
@@ -9092,13 +9889,14 @@
 
 TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) {
     // Interact with the window first.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
 
     // Then remove focus.
     mWindow->setFocusable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
 
     // Assert that caller can switch touch mode by owning one of the last interacted window.
     const WindowInfo& windowInfo = *mWindow->getInfo();
@@ -9140,11 +9938,12 @@
  * Adding a spy window that is not a trusted overlay causes Dispatcher to abort.
  */
 TEST_F(InputDispatcherSpyWindowDeathTest, UntrustedSpy_AbortsDispatcher) {
+    testing::GTEST_FLAG(death_test_style) = "threadsafe";
     ScopedSilentDeath _silentDeath;
 
     auto spy = createSpy();
     spy->setTrustedOverlay(false);
-    ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}}),
+    ASSERT_DEATH(mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0}),
                  ".* not a trusted overlay");
 }
 
@@ -9153,10 +9952,10 @@
  */
 TEST_F(InputDispatcherSpyWindowTest, NoForegroundWindow) {
     auto spy = createSpy();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 }
@@ -9178,7 +9977,8 @@
     auto spy1 = createSpy();
     auto spy2 = createSpy();
     auto spy3 = createSpy();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window, spy3}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo(), *spy3->getInfo()}, {}, 0, 0});
     const std::vector<sp<FakeWindowHandle>> channels{spy1, spy2, window, spy3};
     const size_t numChannels = channels.size();
 
@@ -9195,7 +9995,7 @@
     }
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     std::vector<size_t> eventOrder;
@@ -9230,10 +10030,10 @@
     auto window = createForeground();
     auto spy = createSpy();
     spy->setTouchable(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     spy->assertNoEvents();
@@ -9248,23 +10048,23 @@
     auto window = createForeground();
     auto spy = createSpy();
     spy->setTouchableRegion(Region{{0, 0, 20, 20}});
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Inject an event outside the spy window's touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->assertNoEvents();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionUp();
     spy->assertNoEvents();
 
     // Inject an event inside the spy window's touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {5, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9282,11 +10082,11 @@
     spy->setWatchOutsideTouch(true);
     spy->setOwnerInfo(gui::Pid{56}, gui::Uid{78});
     spy->setFrame(Rect{0, 0, 20, 20});
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Inject an event outside the spy window's frame and touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9304,10 +10104,11 @@
     windowRight->setFrame({100, 0, 200, 200});
     auto spy = createSpy();
     spy->setFrame({0, 0, 200, 200});
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, windowLeft, windowRight}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*spy->getInfo(), *windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowLeft->consumeMotionDown();
@@ -9320,7 +10121,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowRight->consumeMotionDown();
@@ -9336,10 +10137,10 @@
     window->setFrame({0, 0, 200, 200});
     auto spyRight = createSpy();
     spyRight->setFrame({100, 0, 200, 200});
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyRight, window}}});
+    mDispatcher->onWindowInfosChanged({{*spyRight->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9352,7 +10153,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionPointerDown(/*pointerIndex=*/1);
@@ -9372,11 +10173,11 @@
     auto window = createForeground();
     window->setFrame(Rect(0, 0, 100, 100));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // First finger down, no window touched.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -9391,7 +10192,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -9408,15 +10209,15 @@
     spy->setFocusable(false);
 
     auto window = createForeground();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(true);
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeKeyDown(ADISPLAY_ID_NONE);
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeKeyUp(ADISPLAY_ID_NONE);
 
@@ -9433,10 +10234,11 @@
     auto window = createForeground();
     auto spy1 = createSpy();
     auto spy2 = createSpy();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window}}});
+    mDispatcher->onWindowInfosChanged(
+            {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy1->consumeMotionDown();
@@ -9450,7 +10252,7 @@
 
     // The rest of the gesture should only be sent to the second spy window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy2->consumeMotionMove();
@@ -9465,10 +10267,10 @@
 TEST_F(InputDispatcherPilferPointersTest, CanPilferAfterWindowIsRemovedMidStream) {
     auto window = createForeground();
     auto spy = createSpy();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -9478,7 +10280,7 @@
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 }
@@ -9491,11 +10293,11 @@
     auto spy = createSpy();
     auto window = createForeground();
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // First finger down on the window and the spy.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionDown();
@@ -9514,7 +10316,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -9530,7 +10332,7 @@
                     .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(-5).y(-5))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::FAILED";
 
@@ -9547,11 +10349,11 @@
     auto window = createForeground();
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // First finger down on the window only
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {150, 150}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9565,7 +10367,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionDown();
@@ -9581,7 +10383,7 @@
                     .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionPointerDown(1);
@@ -9589,8 +10391,8 @@
 
     // Spy window pilfers the pointers.
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
-    window->consumeMotionPointerUp(/* idx */ 2, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
-    window->consumeMotionPointerUp(/* idx */ 1, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    window->consumeMotionPointerUp(/*idx=*/2, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    window->consumeMotionPointerUp(/*idx=*/1, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
 
     spy->assertNoEvents();
     window->assertNoEvents();
@@ -9607,11 +10409,11 @@
     auto window = createForeground();
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // First finger down on both spy and window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {10, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9626,7 +10428,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionPointerDown(1);
@@ -9650,11 +10452,11 @@
     auto window = createForeground();
     window->setFrame(Rect(0, 0, 200, 200));
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // First finger down on both window and spy
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {10, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9673,7 +10475,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
@@ -9684,6 +10486,72 @@
     spy->assertNoEvents();
 }
 
+/**
+ * A window on the left and a window on the right. Also, a spy window that's above all of the
+ * windows, and spanning both left and right windows.
+ * Send simultaneous motion streams from two different devices, one to the left window, and another
+ * to the right window.
+ * Pilfer from spy window.
+ * Check that the pilfering only affects the pointers that are actually being received by the spy.
+ */
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+    sp<FakeWindowHandle> spy = createSpy();
+    spy->setFrame(Rect(0, 0, 200, 200));
+    sp<FakeWindowHandle> leftWindow = createForeground();
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> rightWindow = createForeground();
+    rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+    constexpr int32_t stylusDeviceId = 1;
+    constexpr int32_t touchDeviceId = 2;
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    // Stylus down on left window and spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Finger down on right window and spy - but spy already has stylus
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spy->assertNoEvents();
+
+    // Act: pilfer from spy. Spy is currently receiving touch events.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+
+    // Continue movements from both stylus and touch. Touch will be delivered to spy, but not stylus
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52))
+                                      .build());
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
+                    .build());
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+
+    spy->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
 class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
 public:
     std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
@@ -9707,7 +10575,7 @@
         window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
 
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+        mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
         setFocusedWindow(window);
         window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
         return {std::move(overlay), std::move(window)};
@@ -9737,13 +10605,14 @@
     auto [overlay, window] = setupStylusOverlayScenario();
     overlay->setTrustedOverlay(false);
     // Configuring an untrusted overlay as a stylus interceptor should cause Dispatcher to abort.
-    ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}}),
+    ASSERT_DEATH(mDispatcher->onWindowInfosChanged(
+                         {{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0}),
                  ".* not a trusted overlay");
 }
 
 TEST_F(InputDispatcherStylusInterceptorTest, ConsmesOnlyStylusEvents) {
     auto [overlay, window] = setupStylusOverlayScenario();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
     overlay->consumeMotionDown();
@@ -9762,7 +10631,7 @@
 TEST_F(InputDispatcherStylusInterceptorTest, SpyWindowStylusInterceptor) {
     auto [overlay, window] = setupStylusOverlayScenario();
     overlay->setSpy(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
     overlay->consumeMotionDown();
@@ -9791,7 +10660,7 @@
 TEST_F(InputDispatcherStylusInterceptorTest, StylusHandwritingScenario) {
     auto [overlay, window] = setupStylusOverlayScenario();
     overlay->setSpy(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
     overlay->consumeMotionDown();
@@ -9803,7 +10672,7 @@
 
     // The interceptor configures itself so that it is no longer a spy.
     overlay->setSpy(false);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // It continues to receive the rest of the stylus gesture.
     sendStylusEvent(AMOTION_EVENT_ACTION_MOVE);
@@ -9824,7 +10693,7 @@
           : mPid(pid), mUid(uid), mDispatcher(dispatcher) {}
 
     InputEventInjectionResult injectTargetedMotion(int32_t action) const {
-        return injectMotionEvent(mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
+        return injectMotionEvent(*mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
                                  ADISPLAY_ID_DEFAULT, {100, 200},
                                  {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                   AMOTION_EVENT_INVALID_CURSOR_POSITION},
@@ -9833,18 +10702,17 @@
     }
 
     InputEventInjectionResult injectTargetedKey(int32_t action) const {
-        return inputdispatcher::injectKey(mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE,
+        return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE,
                                           InputEventInjectionSync::WAIT_FOR_RESULT,
                                           INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid},
                                           mPolicyFlags);
     }
 
-    sp<FakeWindowHandle> createWindow() const {
+    sp<FakeWindowHandle> createWindow(const char* name) const {
         std::shared_ptr<FakeApplicationHandle> overlayApplication =
                 std::make_shared<FakeApplicationHandle>();
-        sp<FakeWindowHandle> window =
-                sp<FakeWindowHandle>::make(overlayApplication, mDispatcher, "Owned Window",
-                                           ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
+                                                                 name, ADISPLAY_ID_DEFAULT);
         window->setOwnerInfo(mPid, mUid);
         return window;
     }
@@ -9854,8 +10722,8 @@
 
 TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) {
     auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
-    auto window = owner.createWindow();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    auto window = owner.createWindow("Owned window");
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
               owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
@@ -9871,8 +10739,8 @@
 
 TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) {
     auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
-    auto window = owner.createWindow();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    auto window = owner.createWindow("Owned window");
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
     EXPECT_EQ(InputEventInjectionResult::TARGET_MISMATCH,
@@ -9888,11 +10756,11 @@
 
 TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedSpyWindow) {
     auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
-    auto window = owner.createWindow();
-    auto spy = owner.createWindow();
+    auto window = owner.createWindow("Owned window");
+    auto spy = owner.createWindow("Owned spy");
     spy->setSpy(true);
     spy->setTrustedOverlay(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
               owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
@@ -9902,13 +10770,13 @@
 
 TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedSpyWindow) {
     auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
-    auto window = owner.createWindow();
+    auto window = owner.createWindow("Owned window");
 
     auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
-    auto randosSpy = rando.createWindow();
+    auto randosSpy = rando.createWindow("Rando's spy");
     randosSpy->setSpy(true);
     randosSpy->setTrustedOverlay(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}});
+    mDispatcher->onWindowInfosChanged({{*randosSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // The event is targeted at owner's window, so injection should succeed, but the spy should
     // not receive the event.
@@ -9920,17 +10788,17 @@
 
 TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTargeting) {
     auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
-    auto window = owner.createWindow();
+    auto window = owner.createWindow("Owned window");
 
     auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
-    auto randosSpy = rando.createWindow();
+    auto randosSpy = rando.createWindow("Rando's spy");
     randosSpy->setSpy(true);
     randosSpy->setTrustedOverlay(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}});
+    mDispatcher->onWindowInfosChanged({{*randosSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // A user that has injection permission can inject into any window.
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                 ADISPLAY_ID_DEFAULT));
     randosSpy->consumeMotionDown();
     window->consumeMotionDown();
@@ -9938,20 +10806,20 @@
     setFocusedWindow(randosSpy);
     randosSpy->consumeFocusEvent(true);
 
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher));
+    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher));
     randosSpy->consumeKeyDown(ADISPLAY_ID_NONE);
     window->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTargetedInjectionTest, CannotGenerateActionOutsideToOtherUids) {
     auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
-    auto window = owner.createWindow();
+    auto window = owner.createWindow("Owned window");
 
     auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
-    auto randosWindow = rando.createWindow();
+    auto randosWindow = rando.createWindow("Rando's window");
     randosWindow->setFrame(Rect{-10, -10, -5, -5});
     randosWindow->setWatchOutsideTouch(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosWindow, window}}});
+    mDispatcher->onWindowInfosChanged({{*randosWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Do not allow generation of ACTION_OUTSIDE events into windows owned by different uids.
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 0eee2b9..dac4ea0 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -27,7 +27,7 @@
 void InputMapperUnitTest::SetUp() {
     mFakePointerController = std::make_shared<FakePointerController>();
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(400, 240);
+    mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
 
     EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
             .WillRepeatedly(Return(mFakePointerController));
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 909bd9c..c2ac258 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -39,6 +39,8 @@
 protected:
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr float INITIAL_CURSOR_X = 400;
+    static constexpr float INITIAL_CURSOR_Y = 240;
     virtual void SetUp() override;
 
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 8941d16..64ae9e8 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -31,8 +31,8 @@
 #include <SensorInputMapper.h>
 #include <SingleTouchInputMapper.h>
 #include <SwitchInputMapper.h>
+#include <TestEventMatchers.h>
 #include <TestInputListener.h>
-#include <TestInputListenerMatchers.h>
 #include <TouchInputMapper.h>
 #include <UinputDevice.h>
 #include <VibratorInputMapper.h>
@@ -164,6 +164,7 @@
     // fake mapping which would normally come from keyCharacterMap
     std::unordered_map<int32_t, int32_t> mKeyCodeMapping;
     std::vector<int32_t> mSupportedKeyCodes;
+    std::list<NotifyArgs> mProcessResult;
 
     std::mutex mLock;
     std::condition_variable mStateChangedCondition;
@@ -194,6 +195,14 @@
         mMetaState = metaState;
     }
 
+    // Sets the return value for the `process` call.
+    void setProcessResult(std::list<NotifyArgs> notifyArgs) {
+        mProcessResult.clear();
+        for (auto notifyArg : notifyArgs) {
+            mProcessResult.push_back(notifyArg);
+        }
+    }
+
     void assertConfigureWasCalled() {
         std::unique_lock<std::mutex> lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
@@ -294,7 +303,7 @@
         mLastEvent = *rawEvent;
         mProcessWasCalled = true;
         mStateChangedCondition.notify_all();
-        return {};
+        return mProcessResult;
     }
 
     int32_t getKeyCodeState(uint32_t, int32_t keyCode) override {
@@ -1863,6 +1872,38 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
 
+/**
+ * Some drivers historically have reported axis values outside of the range specified in the
+ * evdev axis info. Ensure we don't crash when this happens. For example, a driver may report a
+ * pressure value greater than the reported maximum, since it unclear what specific meaning the
+ * maximum value for pressure has (beyond the maximum value that can be produced by a sensor),
+ * and no units for pressure (resolution) is specified by the evdev documentation.
+ */
+TEST_P(TouchIntegrationTest, AcceptsAxisValuesOutsideReportedRange) {
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // Down with pressure outside the reported range
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendPressure(UinputTouchScreen::RAW_PRESSURE_MAX + 2);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    // Move to a point outside the reported range
+    mDevice->sendMove(Point(DISPLAY_WIDTH, DISPLAY_HEIGHT) + Point(1, 1));
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+    // Up
+    mDevice->sendUp();
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+}
+
 TEST_P(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) {
     const Point centerPoint = mDevice->getCenterPoint();
 
@@ -1915,6 +1956,51 @@
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
 }
 
+TEST_P(TouchIntegrationTest, ExternalStylusConnectedDuringTouchGesture) {
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // Down
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    // Move
+    mDevice->sendMove(centerPoint + Point(1, 1));
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+    // Connecting an external stylus mid-gesture should not interrupt the ongoing gesture stream.
+    auto externalStylus = createUinputDevice<UinputExternalStylus>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    const auto stylusInfo = findDeviceByName(externalStylus->getName());
+    ASSERT_TRUE(stylusInfo);
+
+    // Move
+    mDevice->sendMove(centerPoint + Point(2, 2));
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+    // Disconnecting an external stylus mid-gesture should not interrupt the ongoing gesture stream.
+    externalStylus.reset();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+
+    // Up
+    mDevice->sendUp();
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+}
+
 INSTANTIATE_TEST_SUITE_P(TouchIntegrationTestDisplayVariants, TouchIntegrationTest,
                          testing::Values(TouchIntegrationTestDisplays::DISPLAY_INTERNAL,
                                          TouchIntegrationTestDisplays::DISPLAY_INPUT_PORT,
@@ -1978,7 +2064,7 @@
         ::testing::Types<UinputTouchScreen, UinputExternalStylus, UinputExternalStylusWithPressure>;
 TYPED_TEST_SUITE(StylusButtonIntegrationTest, StylusButtonIntegrationTestTypes);
 
-TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsGenerateKeyEvents) {
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsGenerateKeyEvents) {
     const auto stylusId = TestFixture::mStylusInfo.getId();
 
     TestFixture::mStylus->pressKey(BTN_STYLUS);
@@ -1992,7 +2078,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingTouchGesture) {
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingTouchGesture) {
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
     const auto stylusId = TestFixture::mStylusInfo.getId();
@@ -2038,7 +2124,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingHoveringTouchGesture) {
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingHoveringTouchGesture) {
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
     const auto stylusId = TestFixture::mStylusInfo.getId();
@@ -2114,7 +2200,7 @@
                   WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture) {
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsWithinTouchGesture) {
     const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
     const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
     const auto stylusId = TestFixture::mStylusInfo.getId();
@@ -2168,7 +2254,7 @@
                   WithDeviceId(touchscreenId))));
 }
 
-TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisabled) {
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) {
     TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false);
     TestFixture::mReader->requestRefreshConfiguration(
             InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
@@ -2241,7 +2327,7 @@
     ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
 }
 
-TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureReported) {
+TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
     const Point centerPoint = mDevice->getCenterPoint();
 
     // Create an external stylus capable of reporting pressure data that
@@ -2287,7 +2373,7 @@
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
 }
 
-TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotReported) {
+TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) {
     const Point centerPoint = mDevice->getCenterPoint();
 
     // Create an external stylus capable of reporting pressure data that
@@ -2356,7 +2442,7 @@
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
 }
 
-TEST_F(ExternalStylusIntegrationTest, DISABLED_UnfusedExternalStylus) {
+TEST_F(ExternalStylusIntegrationTest, UnfusedExternalStylus) {
     const Point centerPoint = mDevice->getCenterPoint();
 
     // Create an external stylus device that does not support pressure. It should not affect any
@@ -2594,9 +2680,76 @@
     ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled());
 }
 
+TEST_F(InputDeviceTest, WakeDevice_AddsWakeFlagToProcessNotifyArgs) {
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1");
+    FakeInputMapper& mapper =
+            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                AINPUT_SOURCE_KEYBOARD);
+    NotifyMotionArgs args1;
+    NotifySwitchArgs args2;
+    NotifyKeyArgs args3;
+    mapper.setProcessResult({args1, args2, args3});
+
+    InputReaderConfiguration config;
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+    RawEvent event;
+    event.deviceId = EVENTHUB_ID;
+    std::list<NotifyArgs> notifyArgs = mDevice->process(&event, 1);
+
+    for (auto& arg : notifyArgs) {
+        if (const auto notifyMotionArgs = std::get_if<NotifyMotionArgs>(&arg)) {
+            ASSERT_EQ(POLICY_FLAG_WAKE, notifyMotionArgs->policyFlags);
+        } else if (const auto notifySwitchArgs = std::get_if<NotifySwitchArgs>(&arg)) {
+            ASSERT_EQ(POLICY_FLAG_WAKE, notifySwitchArgs->policyFlags);
+        } else if (const auto notifyKeyArgs = std::get_if<NotifyKeyArgs>(&arg)) {
+            ASSERT_EQ(POLICY_FLAG_WAKE, notifyKeyArgs->policyFlags);
+        }
+    }
+}
+
+TEST_F(InputDeviceTest, NotWakeDevice_DoesNotAddWakeFlagToProcessNotifyArgs) {
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "0");
+    FakeInputMapper& mapper =
+            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                AINPUT_SOURCE_KEYBOARD);
+    NotifyMotionArgs args;
+    mapper.setProcessResult({args});
+
+    InputReaderConfiguration config;
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+    RawEvent event;
+    event.deviceId = EVENTHUB_ID;
+    std::list<NotifyArgs> notifyArgs = mDevice->process(&event, 1);
+
+    // POLICY_FLAG_WAKE is not added to the NotifyArgs.
+    ASSERT_EQ(0u, std::get<NotifyMotionArgs>(notifyArgs.front()).policyFlags);
+}
+
+TEST_F(InputDeviceTest, NotWakeDevice_DoesNotRemoveExistingWakeFlagFromProcessNotifyArgs) {
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "0");
+    FakeInputMapper& mapper =
+            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                AINPUT_SOURCE_KEYBOARD);
+    NotifyMotionArgs args;
+    args.policyFlags = POLICY_FLAG_WAKE;
+    mapper.setProcessResult({args});
+
+    InputReaderConfiguration config;
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+    RawEvent event;
+    event.deviceId = EVENTHUB_ID;
+    std::list<NotifyArgs> notifyArgs = mDevice->process(&event, 1);
+
+    // The POLICY_FLAG_WAKE is preserved, despite the device being a non-wake device.
+    ASSERT_EQ(POLICY_FLAG_WAKE, std::get<NotifyMotionArgs>(notifyArgs.front()).policyFlags);
+}
+
 // A single input device is associated with a specific display. Check that:
 // 1. Device is disabled if the viewport corresponding to the associated display is not found
-// 2. Device is disabled when setEnabled API is called
+// 2. Device is disabled when configure API is called
 TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) {
     mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
                                         AINPUT_SOURCE_TOUCHSCREEN);
@@ -2703,7 +2856,8 @@
     mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY);
 
     InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{});
-    device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
+    auto _ = device.addEventHubDevice(ARBITRARY_TIME, TEST_EVENTHUB_ID,
+                                      mFakePolicy->getReaderConfiguration());
     device.removeEventHubDevice(TEST_EVENTHUB_ID);
     std::string dumpStr, eventHubDevStr;
     device.dump(dumpStr, eventHubDevStr);
@@ -3846,6 +4000,19 @@
     ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
 }
 
+TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE);
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    NotifyKeyArgs args;
+
+    // Key down
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFT, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags);
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -4577,92 +4744,6 @@
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
 }
 
-TEST_F(CursorInputMapperTest, Process_PointerCapture) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    mFakePolicy->setPointerCapture(true);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    // Move.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
-
-    // Button press.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-
-    // Button release.
-    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-
-    // Another move.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 30);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 40);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
-
-    // Disable pointer capture and check that the device generation got bumped
-    // and events are generated the usual way.
-    const uint32_t generation = mReader->getContext()->getGeneration();
-    mFakePolicy->setPointerCapture(false);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
 /**
  * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
  * pointer acceleration or speed processing should not be applied.
@@ -9489,6 +9570,11 @@
     EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
 }
 
+/**
+ * When the viewport is deactivated (isActive transitions from true to false),
+ * and touch.enableForInactiveViewport is false, touches prior to the transition
+ * should be cancelled.
+ */
 TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.enableForInactiveViewport", "0");
@@ -9540,6 +9626,60 @@
     EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
 }
 
+/**
+ * When the viewport is deactivated (isActive transitions from true to false),
+ * and touch.enableForInactiveViewport is true, touches prior to the transition
+ * should not be cancelled.
+ */
+TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_TouchesNotAborted) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    addConfigurationProperty("touch.enableForInactiveViewport", "1");
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    std::optional<DisplayViewport> optionalDisplayViewport =
+            mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
+    ASSERT_TRUE(optionalDisplayViewport.has_value());
+    DisplayViewport displayViewport = *optionalDisplayViewport;
+
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+    prepareAxes(POSITION);
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+    // Finger down
+    int32_t x = 100, y = 100;
+    processPosition(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    // Deactivate display viewport
+    displayViewport.isActive = false;
+    ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    // The ongoing touch should not be canceled
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Finger move is not ignored
+    x += 10, y += 10;
+    processPosition(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+    // Reactivate display viewport
+    displayViewport.isActive = true;
+    ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    // Finger move continues and does not start new gesture
+    x += 10, y += 10;
+    processPosition(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+}
+
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
     // Setup the first touch screen device.
     prepareAxes(POSITION | ID | SLOT);
diff --git a/services/inputflinger/tests/InstrumentedInputReader.cpp b/services/inputflinger/tests/InstrumentedInputReader.cpp
index 1f8cd12..110ca5f 100644
--- a/services/inputflinger/tests/InstrumentedInputReader.cpp
+++ b/services/inputflinger/tests/InstrumentedInputReader.cpp
@@ -38,13 +38,13 @@
 }
 
 std::shared_ptr<InputDevice> InstrumentedInputReader::createDeviceLocked(
-        int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) {
+        nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) {
     if (!mNextDevices.empty()) {
         std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
         mNextDevices.pop();
         return device;
     }
-    return InputReader::createDeviceLocked(eventHubId, identifier);
+    return InputReader::createDeviceLocked(when, eventHubId, identifier);
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h
index fef58ec..ca85558 100644
--- a/services/inputflinger/tests/InstrumentedInputReader.h
+++ b/services/inputflinger/tests/InstrumentedInputReader.h
@@ -44,7 +44,7 @@
 
 protected:
     virtual std::shared_ptr<InputDevice> createDeviceLocked(
-            int32_t eventHubId, const InputDeviceIdentifier& identifier);
+            nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier);
 
     class FakeInputReaderContext : public ContextImpl {
     public:
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index b6720c5..05823cd 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -16,8 +16,32 @@
 
 #pragma once
 
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <EventHub.h>
+#include <InputReaderBase.h>
+#include <NotifyArgs.h>
+#include <PointerControllerInterface.h>
+#include <StylusState.h>
+#include <VibrationElement.h>
 #include <android-base/logging.h>
+#include <android-base/result.h>
 #include <gmock/gmock.h>
+#include <input/InputDevice.h>
+#include <input/KeyCharacterMap.h>
+#include <input/KeyLayoutMap.h>
+#include <input/PropertyMap.h>
+#include <input/TouchVideoFrame.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/Errors.h>
+#include <utils/Timers.h>
 
 namespace android {
 
@@ -35,7 +59,7 @@
                 (int32_t deviceId), (override));
 
     MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
-    MOCK_METHOD(int32_t, bumpGeneration, (), (override));
+    int32_t bumpGeneration() override { return ++mGeneration; }
 
     MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo> & outDevices),
                 (override));
@@ -52,6 +76,9 @@
 
     MOCK_METHOD(void, setPreventingTouchpadTaps, (bool prevent), (override));
     MOCK_METHOD(bool, isPreventingTouchpadTaps, (), (override));
+
+private:
+    int32_t mGeneration = 0;
 };
 
 class MockEventHubInterface : public EventHubInterface {
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 08a5559..48f5673 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -112,6 +112,15 @@
 }
 
 /**
+ * Pointer should still hide if touchpad taps are already disabled
+ */
+TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
+    testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
+}
+
+/**
  * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
  * active
  */
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index fa149db..6606de8 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -15,11 +15,14 @@
  */
 
 #include "../dispatcher/LatencyTracker.h"
+#include "../InputDeviceMetricsSource.h"
 
 #include <android-base/properties.h>
 #include <binder/Binder.h>
 #include <gtest/gtest.h>
+#include <gui/constants.h>
 #include <inttypes.h>
+#include <linux/input.h>
 #include <log/log.h>
 
 #define TAG "LatencyTracker_test"
@@ -30,6 +33,29 @@
 
 namespace android::inputdispatcher {
 
+namespace {
+
+constexpr DeviceId DEVICE_ID = 100;
+
+static InputDeviceInfo generateTestDeviceInfo(uint16_t vendorId, uint16_t productId,
+                                              DeviceId deviceId) {
+    InputDeviceIdentifier identifier;
+    identifier.vendor = vendorId;
+    identifier.product = productId;
+    auto info = InputDeviceInfo();
+    info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "Test Device",
+                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+    return info;
+}
+
+void setDefaultInputDeviceInfo(LatencyTracker& tracker) {
+    InputDeviceInfo deviceInfo = generateTestDeviceInfo(
+            /*vendorId=*/0, /*productId=*/0, DEVICE_ID);
+    tracker.setInputDevices({deviceInfo});
+}
+
+} // namespace
+
 const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds(
         android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
         HwTimeoutMultiplier());
@@ -38,7 +64,10 @@
     InputEventTimeline t(
             /*isDown=*/true,
             /*eventTime=*/2,
-            /*readTime=*/3);
+            /*readTime=*/3,
+            /*vendorId=*/0,
+            /*productId=*/0,
+            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
     ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8);
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
@@ -60,6 +89,7 @@
         connection2 = sp<BBinder>::make();
 
         mTracker = std::make_unique<LatencyTracker>(this);
+        setDefaultInputDeviceInfo(*mTracker);
     }
     void TearDown() override {}
 
@@ -88,7 +118,8 @@
     const nsecs_t triggerEventTime =
             lastEventTime + std::chrono::nanoseconds(ANR_TIMEOUT).count() + 1;
     mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/true, triggerEventTime,
-                            /*readTime=*/3);
+                            /*readTime=*/3, DEVICE_ID,
+                            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
 }
 
 void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
@@ -138,9 +169,11 @@
  */
 TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
     mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2,
-                            /*readTime=*/3);
+                            /*readTime=*/3, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
     triggerEventReporting(/*eventTime=*/2);
-    assertReceivedTimeline(InputEventTimeline{false, 2, 3});
+    assertReceivedTimeline(InputEventTimeline{/*isDown=*/false, /*eventTime=*/2,
+                                              /*readTime=*/3, /*vendorId=*/0, /*productID=*/0,
+                                              /*sources=*/{InputDeviceUsageSource::UNKNOWN}});
 }
 
 /**
@@ -171,7 +204,8 @@
 
     const auto& [connectionToken, expectedCT] = *expected.connectionTimelines.begin();
 
-    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime,
+                            DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
     mTracker->trackFinishedEvent(inputEventId, connectionToken, expectedCT.deliveryTime,
                                  expectedCT.consumeTime, expectedCT.finishTime);
     mTracker->trackGraphicsLatency(inputEventId, connectionToken, expectedCT.graphicsTimeline);
@@ -191,8 +225,10 @@
 
     // In the following 2 calls to trackListener, the inputEventId's are the same, but event times
     // are different.
-    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime);
-    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime);
+    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN});
 
     triggerEventReporting(/*eventTime=*/2);
     // Since we sent duplicate input events, the tracker should just delete all of them, because it
@@ -205,7 +241,10 @@
     InputEventTimeline timeline1(
             /*isDown*/ true,
             /*eventTime*/ 2,
-            /*readTime*/ 3);
+            /*readTime*/ 3,
+            /*vendorId=*/0,
+            /*productId=*/0,
+            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
     timeline1.connectionTimelines.emplace(connection1,
                                           ConnectionTimeline(/*deliveryTime*/ 6, /*consumeTime*/ 7,
                                                              /*finishTime*/ 8));
@@ -219,7 +258,10 @@
     InputEventTimeline timeline2(
             /*isDown=*/false,
             /*eventTime=*/20,
-            /*readTime=*/30);
+            /*readTime=*/30,
+            /*vendorId=*/0,
+            /*productId=*/0,
+            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
     timeline2.connectionTimelines.emplace(connection2,
                                           ConnectionTimeline(/*deliveryTime=*/60,
                                                              /*consumeTime=*/70,
@@ -232,10 +274,10 @@
 
     // Start processing first event
     mTracker->trackListener(inputEventId1, timeline1.isDown, timeline1.eventTime,
-                            timeline1.readTime);
+                            timeline1.readTime, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
     // Start processing second event
     mTracker->trackListener(inputEventId2, timeline2.isDown, timeline2.eventTime,
-                            timeline2.readTime);
+                            timeline2.readTime, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
     mTracker->trackFinishedEvent(inputEventId1, connection1, connectionTimeline1.deliveryTime,
                                  connectionTimeline1.consumeTime, connectionTimeline1.finishTime);
 
@@ -261,9 +303,11 @@
 
     for (size_t i = 1; i <= 100; i++) {
         mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime,
-                                timeline.readTime);
-        expectedTimelines.push_back(
-                InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime});
+                                timeline.readTime, /*deviceId=*/DEVICE_ID,
+                                /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+        expectedTimelines.push_back(InputEventTimeline{timeline.isDown, timeline.eventTime,
+                                                       timeline.readTime, timeline.vendorId,
+                                                       timeline.productId, timeline.sources});
     }
     // Now, complete the first event that was sent.
     mTracker->trackFinishedEvent(/*inputEventId=*/1, token, expectedCT.deliveryTime,
@@ -289,10 +333,38 @@
                                  expectedCT.consumeTime, expectedCT.finishTime);
     mTracker->trackGraphicsLatency(inputEventId, connection1, expectedCT.graphicsTimeline);
 
-    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime,
+                            DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
     triggerEventReporting(expected.eventTime);
-    assertReceivedTimeline(
-            InputEventTimeline{expected.isDown, expected.eventTime, expected.readTime});
+    assertReceivedTimeline(InputEventTimeline{expected.isDown, expected.eventTime,
+                                              expected.readTime, expected.vendorId,
+                                              expected.productId, expected.sources});
+}
+
+/**
+ * Check that LatencyTracker has the received timeline that contains the correctly
+ * resolved product ID, vendor ID and source for a particular device ID from
+ * among a list of devices.
+ */
+TEST_F(LatencyTrackerTest, TrackListenerCheck_DeviceInfoFieldsInputEventTimeline) {
+    constexpr int32_t inputEventId = 1;
+    InputEventTimeline timeline(
+            /*isDown*/ true, /*eventTime*/ 2, /*readTime*/ 3,
+            /*vendorId=*/50, /*productId=*/60,
+            /*sources=*/
+            {InputDeviceUsageSource::TOUCHSCREEN, InputDeviceUsageSource::STYLUS_DIRECT});
+    InputDeviceInfo deviceInfo1 = generateTestDeviceInfo(
+            /*vendorId=*/5, /*productId=*/6, /*deviceId=*/DEVICE_ID + 1);
+    InputDeviceInfo deviceInfo2 = generateTestDeviceInfo(
+            /*vendorId=*/50, /*productId=*/60, /*deviceId=*/DEVICE_ID);
+
+    mTracker->setInputDevices({deviceInfo1, deviceInfo2});
+    mTracker->trackListener(inputEventId, timeline.isDown, timeline.eventTime, timeline.readTime,
+                            DEVICE_ID,
+                            {InputDeviceUsageSource::TOUCHSCREEN,
+                             InputDeviceUsageSource::STYLUS_DIRECT});
+    triggerEventReporting(timeline.eventTime);
+    assertReceivedTimeline(timeline);
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
new file mode 100644
index 0000000..7237424
--- /dev/null
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../PointerChoreographer.h"
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#include "TestInputListener.h"
+
+namespace android {
+
+// Helpers to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {};
+template <typename... V>
+Visitor(V...) -> Visitor<V...>;
+
+// --- PointerChoreographerTest ---
+
+class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface {
+protected:
+    TestInputListener mTestListener;
+    PointerChoreographer mChoreographer{mTestListener, *this};
+
+    std::shared_ptr<PointerControllerInterface> createPointerController() { return {}; }
+};
+
+TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
+    const std::vector<NotifyArgs> allArgs{NotifyInputDevicesChangedArgs{},
+                                          NotifyConfigurationChangedArgs{},
+                                          NotifyKeyArgs{},
+                                          NotifyMotionArgs{},
+                                          NotifySensorArgs{},
+                                          NotifySwitchArgs{},
+                                          NotifyDeviceResetArgs{},
+                                          NotifyPointerCaptureChangedArgs{},
+                                          NotifyVibratorStateArgs{}};
+
+    for (auto notifyArgs : allArgs) {
+        mChoreographer.notify(notifyArgs);
+        EXPECT_NO_FATAL_FAILURE(
+                std::visit(Visitor{
+                                   [&](const NotifyInputDevicesChangedArgs& args) {
+                                       mTestListener.assertNotifyInputDevicesChangedWasCalled();
+                                   },
+                                   [&](const NotifyConfigurationChangedArgs& args) {
+                                       mTestListener.assertNotifyConfigurationChangedWasCalled();
+                                   },
+                                   [&](const NotifyKeyArgs& args) {
+                                       mTestListener.assertNotifyKeyWasCalled();
+                                   },
+                                   [&](const NotifyMotionArgs& args) {
+                                       mTestListener.assertNotifyMotionWasCalled();
+                                   },
+                                   [&](const NotifySensorArgs& args) {
+                                       mTestListener.assertNotifySensorWasCalled();
+                                   },
+                                   [&](const NotifySwitchArgs& args) {
+                                       mTestListener.assertNotifySwitchWasCalled();
+                                   },
+                                   [&](const NotifyDeviceResetArgs& args) {
+                                       mTestListener.assertNotifyDeviceResetWasCalled();
+                                   },
+                                   [&](const NotifyPointerCaptureChangedArgs& args) {
+                                       mTestListener.assertNotifyCaptureWasCalled();
+                                   },
+                                   [&](const NotifyVibratorStateArgs& args) {
+                                       mTestListener.assertNotifyVibratorStateWasCalled();
+                                   },
+                           },
+                           notifyArgs));
+    }
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/SlopController_test.cpp b/services/inputflinger/tests/SlopController_test.cpp
new file mode 100644
index 0000000..f524acd
--- /dev/null
+++ b/services/inputflinger/tests/SlopController_test.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../reader/mapper/SlopController.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+// --- SlopControllerTest ---
+
+TEST(SlopControllerTest, PositiveValues) {
+    SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(0, controller.consumeEvent(1000, 1));
+    ASSERT_EQ(0, controller.consumeEvent(1003, 3));
+    ASSERT_EQ(2, controller.consumeEvent(1005, 3));
+    ASSERT_EQ(4, controller.consumeEvent(1009, 4));
+
+    SlopController controller2 = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(0, controller2.consumeEvent(1000, 5));
+    ASSERT_EQ(3, controller2.consumeEvent(1003, 3));
+    ASSERT_EQ(4, controller2.consumeEvent(1005, 4));
+}
+
+TEST(SlopControllerTest, NegativeValues) {
+    SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(0, controller.consumeEvent(1000, -1));
+    ASSERT_EQ(0, controller.consumeEvent(1003, -3));
+    ASSERT_EQ(-2, controller.consumeEvent(1005, -3));
+    ASSERT_EQ(-4, controller.consumeEvent(1009, -4));
+
+    SlopController controller2 = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(0, controller2.consumeEvent(1000, -5));
+    ASSERT_EQ(-3, controller2.consumeEvent(1003, -3));
+    ASSERT_EQ(-4, controller2.consumeEvent(1005, -4));
+}
+
+TEST(SlopControllerTest, ZeroDoesNotResetSlop) {
+    SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(1, controller.consumeEvent(1005, 6));
+    ASSERT_EQ(0, controller.consumeEvent(1006, 0));
+    ASSERT_EQ(2, controller.consumeEvent(1008, 2));
+}
+
+TEST(SlopControllerTest, SignChange_ResetsSlop) {
+    SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(0, controller.consumeEvent(1000, 2));
+    ASSERT_EQ(0, controller.consumeEvent(1001, -4));
+    ASSERT_EQ(0, controller.consumeEvent(1002, 3));
+    ASSERT_EQ(0, controller.consumeEvent(1003, -2));
+
+    ASSERT_EQ(1, controller.consumeEvent(1005, 6));
+    ASSERT_EQ(0, controller.consumeEvent(1006, 0));
+    ASSERT_EQ(2, controller.consumeEvent(1008, 2));
+
+    ASSERT_EQ(0, controller.consumeEvent(1010, -4));
+    ASSERT_EQ(-1, controller.consumeEvent(1011, -2));
+
+    ASSERT_EQ(0, controller.consumeEvent(1015, 5));
+    ASSERT_EQ(2, controller.consumeEvent(1016, 2));
+
+    ASSERT_EQ(0, controller.consumeEvent(1017, -5));
+    ASSERT_EQ(-2, controller.consumeEvent(1018, -2));
+}
+
+TEST(SlopControllerTest, OldAge_ResetsSlop) {
+    SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+    ASSERT_EQ(1, controller.consumeEvent(1005, 6));
+    ASSERT_EQ(0, controller.consumeEvent(1108, 2)); // age exceeds slop duration
+
+    ASSERT_EQ(1, controller.consumeEvent(1110, 4));
+    ASSERT_EQ(0, controller.consumeEvent(1210, 2)); // age equals slop duration
+
+    ASSERT_EQ(0, controller.consumeEvent(1215, -3));
+    ASSERT_EQ(-2, controller.consumeEvent(1216, -4));
+    ASSERT_EQ(-5, controller.consumeEvent(1315, -5));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
new file mode 100644
index 0000000..ee6ff53
--- /dev/null
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cmath>
+#include <compare>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/PrintTools.h>
+
+#include "NotifyArgs.h"
+#include "TestConstants.h"
+
+namespace android {
+
+struct PointF {
+    float x;
+    float y;
+    auto operator<=>(const PointF&) const = default;
+};
+
+inline std::string pointFToString(const PointF& p) {
+    return std::string("(") + std::to_string(p.x) + ", " + std::to_string(p.y) + ")";
+}
+
+/// Source
+class WithSourceMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithSourceMatcher(uint32_t source) : mSource(source) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mSource == args.source;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mSource == args.source;
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mSource == event.getSource();
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with source " << inputEventSourceToString(mSource);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong source"; }
+
+private:
+    const uint32_t mSource;
+};
+
+inline WithSourceMatcher WithSource(uint32_t source) {
+    return WithSourceMatcher(source);
+}
+
+/// Key action
+class WithKeyActionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithKeyActionMatcher(int32_t action) : mAction(action) {}
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mAction == args.action;
+    }
+
+    bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+        return mAction == event.getAction();
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with key action " << KeyEvent::actionToString(mAction);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong action"; }
+
+private:
+    const int32_t mAction;
+};
+
+inline WithKeyActionMatcher WithKeyAction(int32_t action) {
+    return WithKeyActionMatcher(action);
+}
+
+/// Motion action
+class WithMotionActionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        bool matches = mAction == args.action;
+        if (args.action == AMOTION_EVENT_ACTION_CANCEL) {
+            matches &= (args.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+        }
+        return matches;
+    }
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+        bool matches = mAction == event.getAction();
+        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
+            matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with motion action " << MotionEvent::actionToString(mAction);
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            *os << " and FLAG_CANCELED";
+        }
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong action"; }
+
+private:
+    const int32_t mAction;
+};
+
+inline WithMotionActionMatcher WithMotionAction(int32_t action) {
+    return WithMotionActionMatcher(action);
+}
+
+/// Display Id
+class WithDisplayIdMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithDisplayIdMatcher(int32_t displayId) : mDisplayId(displayId) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mDisplayId == args.displayId;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mDisplayId == args.displayId;
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mDisplayId == event.getDisplayId();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with display id " << mDisplayId; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong display id"; }
+
+private:
+    const int32_t mDisplayId;
+};
+
+inline WithDisplayIdMatcher WithDisplayId(int32_t displayId) {
+    return WithDisplayIdMatcher(displayId);
+}
+
+/// Device Id
+class WithDeviceIdMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithDeviceIdMatcher(int32_t deviceId) : mDeviceId(deviceId) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mDeviceId == args.deviceId;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mDeviceId == args.deviceId;
+    }
+
+    bool MatchAndExplain(const NotifyDeviceResetArgs& args, std::ostream*) const {
+        return mDeviceId == args.deviceId;
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mDeviceId == event.getDeviceId();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with device id " << mDeviceId; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong device id"; }
+
+private:
+    const int32_t mDeviceId;
+};
+
+inline WithDeviceIdMatcher WithDeviceId(int32_t deviceId) {
+    return WithDeviceIdMatcher(deviceId);
+}
+
+/// Flags
+class WithFlagsMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithFlagsMatcher(int32_t flags) : mFlags(flags) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mFlags == args.flags;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mFlags == args.flags;
+    }
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+        return mFlags == event.getFlags();
+    }
+
+    bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+        return mFlags == event.getFlags();
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with flags " << base::StringPrintf("0x%x", mFlags);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong flags"; }
+
+private:
+    const int32_t mFlags;
+};
+
+inline WithFlagsMatcher WithFlags(int32_t flags) {
+    return WithFlagsMatcher(flags);
+}
+
+/// DownTime
+class WithDownTimeMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithDownTimeMatcher(nsecs_t downTime) : mDownTime(downTime) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mDownTime == args.downTime;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mDownTime == args.downTime;
+    }
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+        return mDownTime == event.getDownTime();
+    }
+
+    bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+        return mDownTime == event.getDownTime();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with down time " << mDownTime; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong down time"; }
+
+private:
+    const nsecs_t mDownTime;
+};
+
+inline WithDownTimeMatcher WithDownTime(nsecs_t downTime) {
+    return WithDownTimeMatcher(downTime);
+}
+
+/// Coordinate matcher
+class WithCoordsMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithCoordsMatcher(size_t pointerIndex, float x, float y)
+          : mPointerIndex(pointerIndex), mX(x), mY(y) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        if (mPointerIndex >= event.getPointerCount()) {
+            *os << "Pointer index " << mPointerIndex << " is out of bounds";
+            return false;
+        }
+
+        bool matches = mX == event.getX(mPointerIndex) && mY == event.getY(mPointerIndex);
+        if (!matches) {
+            *os << "expected coords (" << mX << ", " << mY << ") at pointer index " << mPointerIndex
+                << ", but got (" << event.getX(mPointerIndex) << ", " << event.getY(mPointerIndex)
+                << ")";
+        }
+        return matches;
+    }
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        if (mPointerIndex >= event.pointerCoords.size()) {
+            *os << "Pointer index " << mPointerIndex << " is out of bounds";
+            return false;
+        }
+
+        bool matches = mX == event.pointerCoords[mPointerIndex].getX() &&
+                mY == event.pointerCoords[mPointerIndex].getY();
+        if (!matches) {
+            *os << "expected coords (" << mX << ", " << mY << ") at pointer index " << mPointerIndex
+                << ", but got (" << event.pointerCoords[mPointerIndex].getX() << ", "
+                << event.pointerCoords[mPointerIndex].getY() << ")";
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with coords (" << mX << ", " << mY << ") at pointer index " << mPointerIndex;
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong coords"; }
+
+private:
+    const size_t mPointerIndex;
+    const float mX;
+    const float mY;
+};
+
+inline WithCoordsMatcher WithCoords(float x, float y) {
+    return WithCoordsMatcher(0, x, y);
+}
+
+inline WithCoordsMatcher WithPointerCoords(size_t pointerIndex, float x, float y) {
+    return WithCoordsMatcher(pointerIndex, x, y);
+}
+
+/// Raw coordinate matcher
+class WithRawCoordsMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithRawCoordsMatcher(size_t pointerIndex, float rawX, float rawY)
+          : mPointerIndex(pointerIndex), mRawX(rawX), mRawY(rawY) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        if (mPointerIndex >= event.getPointerCount()) {
+            *os << "Pointer index " << mPointerIndex << " is out of bounds";
+            return false;
+        }
+
+        bool matches =
+                mRawX == event.getRawX(mPointerIndex) && mRawY == event.getRawY(mPointerIndex);
+        if (!matches) {
+            *os << "expected raw coords (" << mRawX << ", " << mRawY << ") at pointer index "
+                << mPointerIndex << ", but got (" << event.getRawX(mPointerIndex) << ", "
+                << event.getRawY(mPointerIndex) << ")";
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with raw coords (" << mRawX << ", " << mRawY << ") at pointer index "
+            << mPointerIndex;
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong raw coords"; }
+
+private:
+    const size_t mPointerIndex;
+    const float mRawX;
+    const float mRawY;
+};
+
+inline WithRawCoordsMatcher WithRawCoords(float rawX, float rawY) {
+    return WithRawCoordsMatcher(0, rawX, rawY);
+}
+
+inline WithRawCoordsMatcher WithPointerRawCoords(size_t pointerIndex, float rawX, float rawY) {
+    return WithRawCoordsMatcher(pointerIndex, rawX, rawY);
+}
+
+/// Pointer count
+class WithPointerCountMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithPointerCountMatcher(size_t pointerCount) : mPointerCount(pointerCount) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        if (event.getPointerCount() != mPointerCount) {
+            *os << "expected pointer count " << mPointerCount << ", but got "
+                << event.getPointerCount();
+            return false;
+        }
+        return true;
+    }
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        if (event.pointerCoords.size() != mPointerCount) {
+            *os << "expected pointer count " << mPointerCount << ", but got "
+                << event.pointerCoords.size();
+            return false;
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with pointer count " << mPointerCount; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer count"; }
+
+private:
+    const size_t mPointerCount;
+};
+
+inline WithPointerCountMatcher WithPointerCount(size_t pointerCount) {
+    return WithPointerCountMatcher(pointerCount);
+}
+
+/// Pointers matcher
+class WithPointersMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithPointersMatcher(std::map<int32_t, PointF> pointers) : mPointers(pointers) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        std::map<int32_t, PointF> actualPointers;
+        for (size_t pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) {
+            const int32_t pointerId = event.getPointerId(pointerIndex);
+            actualPointers[pointerId] = {event.getX(pointerIndex), event.getY(pointerIndex)};
+        }
+
+        if (mPointers != actualPointers) {
+            *os << "expected pointers " << dumpMap(mPointers, constToString, pointFToString)
+                << ", but got " << dumpMap(actualPointers, constToString, pointFToString);
+            return false;
+        }
+        return true;
+    }
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        std::map<int32_t, PointF> actualPointers;
+        for (size_t pointerIndex = 0; pointerIndex < event.pointerCoords.size(); pointerIndex++) {
+            const int32_t pointerId = event.pointerProperties[pointerIndex].id;
+            actualPointers[pointerId] = {event.pointerCoords[pointerIndex].getX(),
+                                         event.pointerCoords[pointerIndex].getY()};
+        }
+
+        if (mPointers != actualPointers) {
+            *os << "expected pointers " << dumpMap(mPointers, constToString, pointFToString)
+                << ", but got " << dumpMap(actualPointers, constToString, pointFToString);
+            return false;
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with pointers " << dumpMap(mPointers, constToString, pointFToString);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointers"; }
+
+private:
+    const std::map<int32_t, PointF> mPointers;
+};
+
+inline WithPointersMatcher WithPointers(
+        const std::map<int32_t /*id*/, PointF /*coords*/>& pointers) {
+    return WithPointersMatcher(pointers);
+}
+
+MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") {
+    *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode;
+    return arg.keyCode == keyCode;
+}
+
+MATCHER_P(WithRepeatCount, repeatCount, "KeyEvent with specified repeat count") {
+    return arg.getRepeatCount() == repeatCount;
+}
+
+MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") {
+    const auto argPointerId = arg.pointerProperties[index].id;
+    *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId;
+    return argPointerId == id;
+}
+
+MATCHER_P2(WithCursorPosition, x, y, "InputEvent with specified cursor position") {
+    const auto argX = arg.xCursorPosition;
+    const auto argY = arg.yCursorPosition;
+    *result_listener << "expected cursor position (" << x << ", " << y << "), but got (" << argX
+                     << ", " << argY << ")";
+    return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
+}
+
+MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
+    const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
+                     << ", " << argY << ")";
+    return argX == x && argY == y;
+}
+
+MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
+           "InputEvent with specified touchpad gesture offset") {
+    const auto argGestureX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET);
+    const auto argGestureY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET);
+    const double xDiff = fabs(argGestureX - dx);
+    const double yDiff = fabs(argGestureY - dy);
+    *result_listener << "expected gesture offset (" << dx << ", " << dy << ") within " << epsilon
+                     << ", but got (" << argGestureX << ", " << argGestureY << ")";
+    return xDiff <= epsilon && yDiff <= epsilon;
+}
+
+MATCHER_P3(WithGestureScrollDistance, x, y, epsilon,
+           "InputEvent with specified touchpad gesture scroll distance") {
+    const auto argXDistance =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE);
+    const auto argYDistance =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE);
+    const double xDiff = fabs(argXDistance - x);
+    const double yDiff = fabs(argYDistance - y);
+    *result_listener << "expected gesture offset (" << x << ", " << y << ") within " << epsilon
+                     << ", but got (" << argXDistance << ", " << argYDistance << ")";
+    return xDiff <= epsilon && yDiff <= epsilon;
+}
+
+MATCHER_P2(WithGesturePinchScaleFactor, factor, epsilon,
+           "InputEvent with specified touchpad pinch gesture scale factor") {
+    const auto argScaleFactor =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR);
+    *result_listener << "expected gesture scale factor " << factor << " within " << epsilon
+                     << " but got " << argScaleFactor;
+    return fabs(argScaleFactor - factor) <= epsilon;
+}
+
+MATCHER_P(WithGestureSwipeFingerCount, count,
+          "InputEvent with specified touchpad swipe finger count") {
+    const auto argFingerCount =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT);
+    *result_listener << "expected gesture swipe finger count " << count << " but got "
+                     << argFingerCount;
+    return fabs(argFingerCount - count) <= EPSILON;
+}
+
+MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
+    const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+    *result_listener << "expected pressure " << pressure << ", but got " << argPressure;
+    return argPressure == pressure;
+}
+
+MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
+    const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+    const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
+    *result_listener << "expected touch dimensions " << maj << " major x " << min
+                     << " minor, but got " << argMajor << " major x " << argMinor << " minor";
+    return argMajor == maj && argMinor == min;
+}
+
+MATCHER_P2(WithToolDimensions, maj, min, "InputEvent with specified tool dimensions") {
+    const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR);
+    const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR);
+    *result_listener << "expected tool dimensions " << maj << " major x " << min
+                     << " minor, but got " << argMajor << " major x " << argMinor << " minor";
+    return argMajor == maj && argMinor == min;
+}
+
+MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
+    const auto argToolType = arg.pointerProperties[0].toolType;
+    *result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got "
+                     << ftl::enum_string(argToolType);
+    return argToolType == toolType;
+}
+
+MATCHER_P2(WithPointerToolType, pointer, toolType,
+           "InputEvent with specified tool type for pointer") {
+    const auto argToolType = arg.pointerProperties[pointer].toolType;
+    *result_listener << "expected pointer " << pointer << " to have tool type "
+                     << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
+    return argToolType == toolType;
+}
+
+MATCHER_P(WithMotionClassification, classification,
+          "InputEvent with specified MotionClassification") {
+    *result_listener << "expected classification " << motionClassificationToString(classification)
+                     << ", but got " << motionClassificationToString(arg.classification);
+    return arg.classification == classification;
+}
+
+MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") {
+    *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState;
+    return arg.buttonState == buttons;
+}
+
+MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
+    *result_listener << "expected action button " << actionButton << ", but got "
+                     << arg.actionButton;
+    return arg.actionButton == actionButton;
+}
+
+MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") {
+    *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime;
+    return arg.eventTime == eventTime;
+}
+
+MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+    *result_listener << "expected down time " << downTime << ", but got " << arg.downTime;
+    return arg.downTime == downTime;
+}
+
+MATCHER_P2(WithPrecision, xPrecision, yPrecision, "MotionEvent with specified precision") {
+    *result_listener << "expected x-precision " << xPrecision << " and y-precision " << yPrecision
+                     << ", but got " << arg.xPrecision << " and " << arg.yPrecision;
+    return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index fc917dd..41e250f 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -57,6 +57,18 @@
                                            "Expected notifyDeviceReset() to have been called."));
 }
 
+void TestInputListener::clearNotifyDeviceResetCalls() {
+    std::scoped_lock<std::mutex> lock(mLock);
+    std::get<std::vector<NotifyDeviceResetArgs>>(mQueues).clear();
+}
+
+void TestInputListener::assertNotifyDeviceResetWasCalled(
+        const ::testing::Matcher<NotifyDeviceResetArgs>& matcher) {
+    NotifyDeviceResetArgs outEventArgs;
+    ASSERT_NO_FATAL_FAILURE(assertNotifyDeviceResetWasCalled(&outEventArgs));
+    ASSERT_THAT(outEventArgs, matcher);
+}
+
 void TestInputListener::assertNotifyDeviceResetWasNotCalled() {
     ASSERT_NO_FATAL_FAILURE(
             assertNotCalled<NotifyDeviceResetArgs>("notifyDeviceReset() should not be called."));
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index deb6048..3c5e014 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -43,6 +43,10 @@
 
     void assertNotifyConfigurationChangedWasNotCalled();
 
+    void clearNotifyDeviceResetCalls();
+
+    void assertNotifyDeviceResetWasCalled(const ::testing::Matcher<NotifyDeviceResetArgs>& matcher);
+
     void assertNotifyDeviceResetWasCalled(NotifyDeviceResetArgs* outEventArgs = nullptr);
 
     void assertNotifyDeviceResetWasNotCalled();
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
deleted file mode 100644
index 70bad7c..0000000
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cmath>
-
-#include <android/input.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <input/Input.h>
-
-#include "TestConstants.h"
-
-namespace android {
-
-MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
-    bool matches = action == arg.action;
-    if (!matches) {
-        *result_listener << "expected action " << MotionEvent::actionToString(action)
-                         << ", but got " << MotionEvent::actionToString(arg.action);
-    }
-    if (action == AMOTION_EVENT_ACTION_CANCEL) {
-        if (!matches) {
-            *result_listener << "; ";
-        }
-        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
-        matches &= (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
-    }
-    return matches;
-}
-
-MATCHER_P(WithKeyAction, action, "KeyEvent with specified action") {
-    *result_listener << "expected action " << KeyEvent::actionToString(action) << ", but got "
-                     << KeyEvent::actionToString(arg.action);
-    return arg.action == action;
-}
-
-MATCHER_P(WithSource, source, "InputEvent with specified source") {
-    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
-                     << inputEventSourceToString(arg.source);
-    return arg.source == source;
-}
-
-MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
-    *result_listener << "expected displayId " << displayId << ", but got " << arg.displayId;
-    return arg.displayId == displayId;
-}
-
-MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") {
-    *result_listener << "expected deviceId " << deviceId << ", but got " << arg.deviceId;
-    return arg.deviceId == deviceId;
-}
-
-MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") {
-    *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode;
-    return arg.keyCode == keyCode;
-}
-
-MATCHER_P(WithPointerCount, count, "MotionEvent with specified number of pointers") {
-    *result_listener << "expected " << count << " pointer(s), but got " << arg.getPointerCount();
-    return arg.getPointerCount() == count;
-}
-
-MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") {
-    const auto argPointerId = arg.pointerProperties[index].id;
-    *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId;
-    return argPointerId == id;
-}
-
-MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") {
-    const auto argX = arg.pointerCoords[0].getX();
-    const auto argY = arg.pointerCoords[0].getY();
-    *result_listener << "expected coords (" << x << ", " << y << "), but got (" << argX << ", "
-                     << argY << ")";
-    return argX == x && argY == y;
-}
-
-MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") {
-    const auto argX = arg.pointerCoords[pointer].getX();
-    const auto argY = arg.pointerCoords[pointer].getY();
-    *result_listener << "expected pointer " << pointer << " to have coords (" << x << ", " << y
-                     << "), but got (" << argX << ", " << argY << ")";
-    return argX == x && argY == y;
-}
-
-MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
-    const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
-                     << ", " << argY << ")";
-    return argX == x && argY == y;
-}
-
-MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
-           "InputEvent with specified touchpad gesture offset") {
-    const auto argGestureX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET);
-    const auto argGestureY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET);
-    const double xDiff = fabs(argGestureX - dx);
-    const double yDiff = fabs(argGestureY - dy);
-    *result_listener << "expected gesture offset (" << dx << ", " << dy << ") within " << epsilon
-                     << ", but got (" << argGestureX << ", " << argGestureY << ")";
-    return xDiff <= epsilon && yDiff <= epsilon;
-}
-
-MATCHER_P3(WithGestureScrollDistance, x, y, epsilon,
-           "InputEvent with specified touchpad gesture scroll distance") {
-    const auto argXDistance =
-            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE);
-    const auto argYDistance =
-            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE);
-    const double xDiff = fabs(argXDistance - x);
-    const double yDiff = fabs(argYDistance - y);
-    *result_listener << "expected gesture offset (" << x << ", " << y << ") within " << epsilon
-                     << ", but got (" << argXDistance << ", " << argYDistance << ")";
-    return xDiff <= epsilon && yDiff <= epsilon;
-}
-
-MATCHER_P2(WithGesturePinchScaleFactor, factor, epsilon,
-           "InputEvent with specified touchpad pinch gesture scale factor") {
-    const auto argScaleFactor =
-            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR);
-    *result_listener << "expected gesture scale factor " << factor << " within " << epsilon
-                     << " but got " << argScaleFactor;
-    return fabs(argScaleFactor - factor) <= epsilon;
-}
-
-MATCHER_P(WithGestureSwipeFingerCount, count,
-          "InputEvent with specified touchpad swipe finger count") {
-    const auto argFingerCount =
-            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT);
-    *result_listener << "expected gesture swipe finger count " << count << " but got "
-                     << argFingerCount;
-    return fabs(argFingerCount - count) <= EPSILON;
-}
-
-MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
-    const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
-    *result_listener << "expected pressure " << pressure << ", but got " << argPressure;
-    return argPressure == pressure;
-}
-
-MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
-    const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
-    const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
-    *result_listener << "expected touch dimensions " << maj << " major x " << min
-                     << " minor, but got " << argMajor << " major x " << argMinor << " minor";
-    return argMajor == maj && argMinor == min;
-}
-
-MATCHER_P2(WithToolDimensions, maj, min, "InputEvent with specified tool dimensions") {
-    const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR);
-    const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR);
-    *result_listener << "expected tool dimensions " << maj << " major x " << min
-                     << " minor, but got " << argMajor << " major x " << argMinor << " minor";
-    return argMajor == maj && argMinor == min;
-}
-
-MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
-    const auto argToolType = arg.pointerProperties[0].toolType;
-    *result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got "
-                     << ftl::enum_string(argToolType);
-    return argToolType == toolType;
-}
-
-MATCHER_P2(WithPointerToolType, pointer, toolType,
-           "InputEvent with specified tool type for pointer") {
-    const auto argToolType = arg.pointerProperties[pointer].toolType;
-    *result_listener << "expected pointer " << pointer << " to have tool type "
-                     << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
-    return argToolType == toolType;
-}
-
-MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
-    *result_listener << "expected flags " << flags << ", but got " << arg.flags;
-    return arg.flags == static_cast<int32_t>(flags);
-}
-
-MATCHER_P(WithMotionClassification, classification,
-          "InputEvent with specified MotionClassification") {
-    *result_listener << "expected classification " << motionClassificationToString(classification)
-                     << ", but got " << motionClassificationToString(arg.classification);
-    return arg.classification == classification;
-}
-
-MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") {
-    *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState;
-    return arg.buttonState == buttons;
-}
-
-MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
-    *result_listener << "expected action button " << actionButton << ", but got "
-                     << arg.actionButton;
-    return arg.actionButton == actionButton;
-}
-
-MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") {
-    *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime;
-    return arg.eventTime == eventTime;
-}
-
-MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
-    *result_listener << "expected down time " << downTime << ", but got " << arg.downTime;
-    return arg.downTime == downTime;
-}
-
-MATCHER_P2(WithPrecision, xPrecision, yPrecision, "MotionEvent with specified precision") {
-    *result_listener << "expected x-precision " << xPrecision << " and y-precision " << yPrecision
-                     << ", but got " << arg.xPrecision << " and " << arg.yPrecision;
-    return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision;
-}
-
-} // namespace android
diff --git a/services/inputflinger/tests/TimerProvider_test.cpp b/services/inputflinger/tests/TimerProvider_test.cpp
new file mode 100644
index 0000000..cb28823
--- /dev/null
+++ b/services/inputflinger/tests/TimerProvider_test.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gestures/TimerProvider.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "InterfaceMocks.h"
+#include "TestConstants.h"
+#include "include/gestures.h"
+
+namespace android {
+
+namespace {
+
+class TestTimerProvider : public TimerProvider {
+public:
+    TestTimerProvider(InputReaderContext& context) : TimerProvider(context) {}
+
+    void setCurrentTime(nsecs_t time) { mCurrentTime = time; }
+
+protected:
+    nsecs_t getCurrentTime() override { return mCurrentTime; }
+
+private:
+    nsecs_t mCurrentTime = 0;
+};
+
+stime_t pushTimeOntoVector(stime_t triggerTime, void* data) {
+    std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(data);
+    times->push_back(triggerTime);
+    return NO_DEADLINE;
+}
+
+stime_t copyTimeToVariable(stime_t triggerTime, void* data) {
+    stime_t* time = static_cast<stime_t*>(data);
+    *time = triggerTime;
+    return NO_DEADLINE;
+}
+
+stime_t incrementInt(stime_t triggerTime, void* data) {
+    int* count = static_cast<int*>(data);
+    *count += 1;
+    return NO_DEADLINE;
+}
+
+} // namespace
+
+using testing::AtLeast;
+
+class TimerProviderTest : public testing::Test {
+public:
+    TimerProviderTest() : mProvider(mMockContext) {}
+
+protected:
+    void triggerCallbacksWithFakeTime(nsecs_t time) {
+        mProvider.setCurrentTime(time);
+        mProvider.triggerCallbacks(time);
+    }
+
+    MockInputReaderContext mMockContext;
+    TestTimerProvider mProvider;
+};
+
+TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsExactlyOnTime) {
+    GesturesTimer* timer = mProvider.createTimer();
+    std::vector<stime_t> callTimes;
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(3);
+
+    // Call through kGestureTimerProvider in this test case, so that we cover the stime_t to nsecs_t
+    // conversion code. This is why the delay is 1.0 rather than 1'000'000'000 here.
+    kGestureTimerProvider.set_fn(&mProvider, timer, 1.0, &pushTimeOntoVector, &callTimes);
+
+    triggerCallbacksWithFakeTime(900'000'000);
+    triggerCallbacksWithFakeTime(999'999'999);
+    EXPECT_EQ(0u, callTimes.size());
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    ASSERT_EQ(1u, callTimes.size());
+    EXPECT_NEAR(1.0, callTimes[0], EPSILON);
+
+    // Now that the timer has triggered, it shouldn't trigger again if we get another timeout from
+    // InputReader.
+    triggerCallbacksWithFakeTime(1'300'000'000);
+    EXPECT_EQ(1u, callTimes.size());
+}
+
+TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsLate) {
+    GesturesTimer* timer = mProvider.createTimer();
+    stime_t callTime = -1.0;
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime);
+
+    triggerCallbacksWithFakeTime(1'010'000'000);
+    EXPECT_NEAR(1.01, callTime, EPSILON);
+}
+
+TEST_F(TimerProviderTest, SingleRescheduledDeadlineTriggers) {
+    GesturesTimer* timer = mProvider.createTimer();
+    std::vector<stime_t> callTimes;
+    auto callback = [](stime_t triggerTime, void* callbackData) {
+        std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(callbackData);
+        times->push_back(triggerTime);
+        if (times->size() < 2) {
+            return 1.0;
+        } else {
+            return NO_DEADLINE;
+        }
+    };
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+    // The deadline should be rescheduled for 2.01s, since the first triggerCallbacks call is 0.01s
+    // late.
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(2'010'000'000)).Times(1);
+
+    mProvider.setDeadline(timer, 1'000'000'000, callback, &callTimes);
+
+    triggerCallbacksWithFakeTime(1'010'000'000);
+    ASSERT_EQ(1u, callTimes.size());
+    EXPECT_NEAR(1.01, callTimes[0], EPSILON);
+
+    triggerCallbacksWithFakeTime(2'020'000'000);
+    ASSERT_EQ(2u, callTimes.size());
+    EXPECT_NEAR(1.01, callTimes[0], EPSILON);
+    EXPECT_NEAR(2.02, callTimes[1], EPSILON);
+
+    triggerCallbacksWithFakeTime(3'000'000'000);
+    EXPECT_EQ(2u, callTimes.size());
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithMultipleTimeouts) {
+    GesturesTimer* timer = mProvider.createTimer();
+    std::vector<stime_t> callTimes1;
+    std::vector<stime_t> callTimes2;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1);
+
+    mProvider.setDeadline(timer, 1'000'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer, 1'500'000'000, &pushTimeOntoVector, &callTimes2);
+
+    EXPECT_EQ(0u, callTimes1.size());
+    EXPECT_EQ(0u, callTimes2.size());
+
+    triggerCallbacksWithFakeTime(1'010'000'000);
+    ASSERT_EQ(1u, callTimes1.size());
+    EXPECT_NEAR(1.01, callTimes1[0], EPSILON);
+    EXPECT_EQ(0u, callTimes2.size());
+
+    triggerCallbacksWithFakeTime(1'500'000'000);
+    EXPECT_EQ(1u, callTimes1.size());
+    ASSERT_EQ(1u, callTimes2.size());
+    EXPECT_NEAR(1.5, callTimes2[0], EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithOneLateTimeout) {
+    GesturesTimer* timer = mProvider.createTimer();
+    stime_t callTime1 = -1.0;
+    stime_t callTime2 = -1.0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime1);
+    mProvider.setDeadline(timer, 1'500'000'000, &copyTimeToVariable, &callTime2);
+
+    triggerCallbacksWithFakeTime(1'510'000'000);
+    EXPECT_NEAR(1.51, callTime1, EPSILON);
+    EXPECT_NEAR(1.51, callTime2, EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesAtSameTimeTriggerTogether) {
+    GesturesTimer* timer = mProvider.createTimer();
+    stime_t callTime1 = -1.0;
+    stime_t callTime2 = -1.0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime1);
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime2);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_NEAR(1.0, callTime1, EPSILON);
+    EXPECT_NEAR(1.0, callTime2, EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleTimersTriggerCorrectly) {
+    GesturesTimer* timer1 = mProvider.createTimer();
+    GesturesTimer* timer2 = mProvider.createTimer();
+    std::vector<stime_t> callTimes1;
+    std::vector<stime_t> callTimes2;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'250'000'000)).Times(1);
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1);
+
+    mProvider.setDeadline(timer1, 500'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer1, 1'250'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer1, 1'500'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer2, 750'000'000, &pushTimeOntoVector, &callTimes2);
+    mProvider.setDeadline(timer2, 1'250'000'000, &pushTimeOntoVector, &callTimes2);
+
+    triggerCallbacksWithFakeTime(800'000'000);
+    ASSERT_EQ(1u, callTimes1.size());
+    EXPECT_NEAR(0.8, callTimes1[0], EPSILON);
+    ASSERT_EQ(1u, callTimes2.size());
+    EXPECT_NEAR(0.8, callTimes2[0], EPSILON);
+
+    triggerCallbacksWithFakeTime(1'250'000'000);
+    ASSERT_EQ(2u, callTimes1.size());
+    EXPECT_NEAR(1.25, callTimes1[1], EPSILON);
+    ASSERT_EQ(2u, callTimes2.size());
+    EXPECT_NEAR(1.25, callTimes2[1], EPSILON);
+
+    triggerCallbacksWithFakeTime(1'501'000'000);
+    ASSERT_EQ(3u, callTimes1.size());
+    EXPECT_NEAR(1.501, callTimes1[2], EPSILON);
+    EXPECT_EQ(2u, callTimes2.size());
+}
+
+TEST_F(TimerProviderTest, CancelledTimerDoesntTrigger) {
+    GesturesTimer* timer = mProvider.createTimer();
+    int numCalls = 0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+    mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCalls);
+    mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls);
+    mProvider.cancelTimer(timer);
+
+    triggerCallbacksWithFakeTime(1'100'000'000);
+    EXPECT_EQ(0, numCalls);
+}
+
+TEST_F(TimerProviderTest, CancellingOneTimerDoesntAffectOthers) {
+    GesturesTimer* timer1 = mProvider.createTimer();
+    GesturesTimer* timer2 = mProvider.createTimer();
+    int numCalls1 = 0;
+    int numCalls2 = 0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+
+    mProvider.setDeadline(timer1, 500'000'000, &incrementInt, &numCalls1);
+    mProvider.setDeadline(timer2, 500'000'000, &incrementInt, &numCalls2);
+    mProvider.setDeadline(timer2, 1'000'000'000, &incrementInt, &numCalls2);
+    mProvider.cancelTimer(timer1);
+
+    triggerCallbacksWithFakeTime(501'000'000);
+    EXPECT_EQ(0, numCalls1);
+    EXPECT_EQ(1, numCalls2);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_EQ(0, numCalls1);
+    EXPECT_EQ(2, numCalls2);
+}
+
+TEST_F(TimerProviderTest, CancellingOneTimerCausesNewTimeoutRequestForAnother) {
+    GesturesTimer* timer1 = mProvider.createTimer();
+    GesturesTimer* timer2 = mProvider.createTimer();
+    auto callback = [](stime_t, void*) { return NO_DEADLINE; };
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+
+    mProvider.setDeadline(timer1, 500'000'000, callback, nullptr);
+    mProvider.setDeadline(timer2, 1'000'000'000, callback, nullptr);
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+    mProvider.cancelTimer(timer1);
+}
+
+TEST_F(TimerProviderTest, CancelledTimerCanBeReused) {
+    GesturesTimer* timer = mProvider.createTimer();
+    int numCallsBeforeCancellation = 0;
+    int numCallsAfterCancellation = 0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(1);
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+
+    mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCallsBeforeCancellation);
+    mProvider.cancelTimer(timer);
+    mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCallsAfterCancellation);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_EQ(0, numCallsBeforeCancellation);
+    EXPECT_EQ(1, numCallsAfterCancellation);
+}
+
+TEST_F(TimerProviderTest, FreeingTimerCancelsFirst) {
+    GesturesTimer* timer = mProvider.createTimer();
+    int numCalls = 0;
+
+    mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls);
+    mProvider.freeTimer(timer);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_EQ(0, numCalls);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 02abf9f..6203a1d 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -23,7 +23,7 @@
 #include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
-#include "TestInputListenerMatchers.h"
+#include "TestEventMatchers.h"
 
 #define TAG "TouchpadInputMapper_test"
 
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index e8aaa18..acc7023 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -189,6 +189,7 @@
     ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
     ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
     ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
+    ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
     ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
     if (!mPhysicalPort.empty()) {
         ioctl(fd, UI_SET_PHYS, mPhysicalPort.c_str());
@@ -206,6 +207,8 @@
     device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX;
     device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER;
     device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX;
+    device->absmin[ABS_MT_PRESSURE] = RAW_PRESSURE_MIN;
+    device->absmax[ABS_MT_PRESSURE] = RAW_PRESSURE_MAX;
 }
 
 void UinputTouchScreen::sendSlot(int32_t slot) {
@@ -218,6 +221,7 @@
 
 void UinputTouchScreen::sendDown(const Point& point) {
     injectEvent(EV_KEY, BTN_TOUCH, 1);
+    injectEvent(EV_ABS, ABS_MT_PRESSURE, RAW_PRESSURE_MAX);
     injectEvent(EV_ABS, ABS_MT_POSITION_X, point.x);
     injectEvent(EV_ABS, ABS_MT_POSITION_Y, point.y);
 }
@@ -227,6 +231,10 @@
     injectEvent(EV_ABS, ABS_MT_POSITION_Y, point.y);
 }
 
+void UinputTouchScreen::sendPressure(int32_t pressure) {
+    injectEvent(EV_ABS, ABS_MT_PRESSURE, pressure);
+}
+
 void UinputTouchScreen::sendPointerUp() {
     sendTrackingId(0xffffffff);
 }
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
index f5507ec..d4b4e77 100644
--- a/services/inputflinger/tests/UinputDevice.h
+++ b/services/inputflinger/tests/UinputDevice.h
@@ -206,6 +206,7 @@
     void sendTrackingId(int32_t trackingId);
     void sendDown(const Point& point);
     void sendMove(const Point& point);
+    void sendPressure(int32_t pressure);
     void sendPointerUp();
     void sendUp();
     void sendToolType(int32_t toolType);
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index da0815f..78f7291 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -23,8 +23,8 @@
 #include <thread>
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
 
+#include "TestEventMatchers.h"
 #include "TestInputListener.h"
-#include "TestInputListenerMatchers.h"
 
 using ::testing::AllOf;
 
@@ -88,14 +88,13 @@
     }
 
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, eventTime, /*readTime=*/0, DEVICE_ID,
-                          AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, POLICY_FLAG_PASS_TO_USER,
-                          action, /* actionButton */ 0,
-                          /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
-                          MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
-                          pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+    NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+                          /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0,
+                          /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
+                          AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
+                          pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                          AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /*videoFrames=*/{});
 
     return args;
 }
@@ -104,15 +103,15 @@
     InputDeviceIdentifier identifier;
 
     auto info = InputDeviceInfo();
-    info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
-                    /*isExternal*/ false, /*hasMic*/ false, ADISPLAY_ID_NONE);
+    info.initialize(DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias",
+                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
     info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
-    info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat*/ 0,
-                        /*fuzz*/ 0, X_RESOLUTION);
-    info.addMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN, 0, 2559, /*flat*/ 0,
-                        /*fuzz*/ 0, Y_RESOLUTION);
+    info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat=*/0,
+                        /*fuzz=*/0, X_RESOLUTION);
+    info.addMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN, 0, 2559, /*flat=*/0,
+                        /*fuzz=*/0, Y_RESOLUTION);
     info.addMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN, 0, 255,
-                        /*flat*/ 0, /*fuzz*/ 0, MAJOR_RESOLUTION);
+                        /*flat=*/0, /*fuzz=*/0, MAJOR_RESOLUTION);
 
     return info;
 }
@@ -152,7 +151,7 @@
 }
 
 TEST(RemovePointerIdsTest, RemoveOnePointer) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0,
                                                AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
 
     NotifyMotionArgs pointer1Only = removePointerIds(args, {0});
@@ -167,7 +166,7 @@
  */
 TEST(RemovePointerIdsTest, RemoveTwoPointers) {
     NotifyMotionArgs args =
-            generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, AMOTION_EVENT_ACTION_MOVE,
+            generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, AMOTION_EVENT_ACTION_MOVE,
                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
 
     NotifyMotionArgs pointer1Only = removePointerIds(args, {0, 2});
@@ -179,7 +178,7 @@
  * pointer during a POINTER_DOWN event.
  */
 TEST(RemovePointerIdsTest, ActionPointerDown) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_DOWN,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
 
     NotifyMotionArgs pointers0And2 = removePointerIds(args, {1});
@@ -193,7 +192,7 @@
  * Remove all pointers during a MOVE event.
  */
 TEST(RemovePointerIdsTest, RemoveAllPointersDuringMove) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0,
                                                AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
 
     NotifyMotionArgs noPointers = removePointerIds(args, {0, 1});
@@ -205,7 +204,7 @@
  * then we should just have ACTION_DOWN. Likewise, a POINTER_UP event should become an UP event.
  */
 TEST(RemovePointerIdsTest, PointerDownBecomesDown) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_DOWN,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
 
     NotifyMotionArgs pointer1 = removePointerIds(args, {0, 2});
@@ -220,11 +219,11 @@
  * If a pointer that is now going down is canceled, then we can just drop the POINTER_DOWN event.
  */
 TEST(CancelSuppressedPointersTest, CanceledPointerDownIsDropped) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_DOWN,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
-                                     /*newSuppressedPointerIds*/ {1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+                                     /*newSuppressedPointerIds=*/{1});
     ASSERT_TRUE(result.empty());
 }
 
@@ -232,11 +231,11 @@
  * If a pointer is already suppressed, the POINTER_UP event for this pointer should be dropped
  */
 TEST(CancelSuppressedPointersTest, SuppressedPointerUpIsDropped) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_UP,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
-                                     /*newSuppressedPointerIds*/ {1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{1},
+                                     /*newSuppressedPointerIds=*/{1});
     ASSERT_TRUE(result.empty());
 }
 
@@ -244,11 +243,11 @@
  * If a pointer is already suppressed, it should be removed from a MOVE event.
  */
 TEST(CancelSuppressedPointersTest, SuppressedPointerIsRemovedDuringMove) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
-                                     /*newSuppressedPointerIds*/ {1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{1},
+                                     /*newSuppressedPointerIds=*/{1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
 }
@@ -259,11 +258,11 @@
  * 2) A MOVE event without this pointer
  */
 TEST(CancelSuppressedPointersTest, NewlySuppressedPointerIsCanceled) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
-                                     /*newSuppressedPointerIds*/ {1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+                                     /*newSuppressedPointerIds=*/{1});
     ASSERT_EQ(2u, result.size());
     assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
     ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -275,10 +274,10 @@
  * should be canceled with ACTION_CANCEL.
  */
 TEST(CancelSuppressedPointersTest, SingleSuppressedPointerIsCanceled) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}});
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE, {{1, 2, 3}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
-                                     /*newSuppressedPointerIds*/ {0});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+                                     /*newSuppressedPointerIds=*/{0});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
     ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -289,11 +288,11 @@
  * but this event should also have FLAG_CANCELED to indicate that this pointer was unintentional.
  */
 TEST(CancelSuppressedPointersTest, SuppressedPointer1GoingUpIsCanceled) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_UP,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
-                                     /*newSuppressedPointerIds*/ {1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+                                     /*newSuppressedPointerIds=*/{1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
     ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -304,11 +303,11 @@
  * errors with handling pointer index inside the action.
  */
 TEST(CancelSuppressedPointersTest, SuppressedPointer0GoingUpIsCanceled) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_0_UP,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_0_UP,
                                                {{1, 2, 3}, {4, 5, 6}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
-                                     /*newSuppressedPointerIds*/ {0});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+                                     /*newSuppressedPointerIds=*/{0});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], POINTER_0_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
     ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -320,10 +319,10 @@
  */
 TEST(CancelSuppressedPointersTest, TwoNewlySuppressedPointersAreBothCanceled) {
     NotifyMotionArgs args =
-            generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}, {4, 5, 6}});
+            generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE, {{1, 2, 3}, {4, 5, 6}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
-                                     /*newSuppressedPointerIds*/ {0, 1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+                                     /*newSuppressedPointerIds=*/{0, 1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
     ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -335,11 +334,11 @@
  * would undo the entire gesture.
  */
 TEST(CancelSuppressedPointersTest, TwoPointersAreCanceledDuringPointerUp) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_UP,
                                                {{1, 2, 3}, {4, 5, 6}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
-                                     /*newSuppressedPointerIds*/ {0, 1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{1},
+                                     /*newSuppressedPointerIds=*/{0, 1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
     ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -350,11 +349,11 @@
  * this should become a regular DOWN event because it's the only pointer that will be valid now.
  */
 TEST(CancelSuppressedPointersTest, NewPointerDownBecomesDown) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_2_DOWN,
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_2_DOWN,
                                                {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
     std::vector<NotifyMotionArgs> result =
-            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {0, 1},
-                                     /*newSuppressedPointerIds*/ {0, 1});
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{0, 1},
+                                     /*newSuppressedPointerIds=*/{0, 1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], DOWN, {{2, {7, 8, 9}}});
     ASSERT_EQ(0, result[0].flags);
@@ -365,7 +364,7 @@
  * struct is populated as expected.
  */
 TEST(GetTouchesTest, ConvertDownEvent) {
-    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, DOWN, {{1, 2, 3}});
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     AndroidPalmFilterDeviceInfo deviceInfo = generatePalmFilterDeviceInfo();
     SlotState slotState;
     SlotState oldSlotState = slotState;
@@ -1024,7 +1023,7 @@
     mPalmRejector->processMotion(
             generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+            generateMotionArgs(downTime, /*eventTime=*/1, POINTER_1_DOWN,
                                {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
     // Suppress both pointers!!
     suppressPointerAtPosition(1414, 702);
@@ -1058,13 +1057,13 @@
     mPalmRejector->processMotion(
             generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+            generateMotionArgs(downTime, /*eventTime=*/1, POINTER_1_DOWN,
                                {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
 
     // Suppress second pointer (pointer 1)
     suppressPointerAtPosition(1060, 700);
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, /*eventTime*/ 1, MOVE,
+            generateMotionArgs(downTime, /*eventTime=*/1, MOVE,
                                {{1417.0, 685.0, 41.0}, {1060, 700, 10.0}}));
     ASSERT_EQ(2u, argsList.size());
     ASSERT_EQ(POINTER_1_UP, argsList[0].action);
@@ -1076,20 +1075,20 @@
     // A new pointer goes down and gets suppressed right away. It should just be dropped
     suppressPointerAtPosition(1001, 601);
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_DOWN,
+            generateMotionArgs(downTime, /*eventTime=*/1, POINTER_2_DOWN,
                                {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
 
     ASSERT_EQ(0u, argsList.size());
     // Likewise, pointer that's already canceled should be ignored
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_UP,
+            generateMotionArgs(downTime, /*eventTime=*/1, POINTER_2_UP,
                                {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
     ASSERT_EQ(0u, argsList.size());
 
     // Cancel all pointers when pointer 1 goes up. Pointer 1 was already canceled earlier.
     suppressPointerAtPosition(1417, 685);
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_UP,
+            generateMotionArgs(downTime, /*eventTime=*/1, POINTER_1_UP,
                                {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
     ASSERT_EQ(1u, argsList.size());
     ASSERT_EQ(CANCEL, argsList[0].action);
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 47b0824..9313a89 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -56,6 +56,15 @@
     ],
     fuzz_config: {
         cc: ["android-framework-input@google.com"],
+        componentid: 155276,
+        hotlists: [
+            "4593311",
+        ],
+        description: "The fuzzer targets the APIs of libinputflinger library",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
     },
 }
 
@@ -100,6 +109,22 @@
 }
 
 cc_fuzz {
+    name: "inputflinger_touchpad_input_fuzzer",
+    defaults: [
+        "inputflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "TouchpadInputFuzzer.cpp",
+    ],
+    static_libs: [
+        "libchrome-gestures",
+    ],
+    header_libs: [
+        "libchrome-gestures_headers",
+    ],
+}
+
+cc_fuzz {
     name: "inputflinger_input_reader_fuzzer",
     defaults: [
         "inputflinger_fuzz_defaults",
diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
index e9016bb..219b662 100644
--- a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
@@ -50,8 +50,9 @@
                     // Pops blocks if it is empty, so only pop up to num elements inserted.
                     size_t numPops = fdp.ConsumeIntegralInRange<size_t>(0, filled);
                     for (size_t i = 0; i < numPops; i++) {
-                        queue.popWithTimeout(
-                                std::chrono::nanoseconds{fdp.ConsumeIntegral<int64_t>()});
+                        // Provide a random timeout up to 1 second
+                        queue.popWithTimeout(std::chrono::nanoseconds(
+                                fdp.ConsumeIntegralInRange<int64_t>(0, 1E9)));
                     }
                     filled > numPops ? filled -= numPops : filled = 0;
                 },
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index 8098ef2..af20a27 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -15,36 +15,47 @@
  */
 
 #include <CursorInputMapper.h>
-#include <FuzzContainer.h>
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
 
 namespace android {
 
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
+static void addProperty(FuzzEventHub& eventHub, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
     // Pick a random property to set for the mapper to have set.
     fdp->PickValueInArray<std::function<void()>>(
-            {[&]() -> void { fuzzer.addProperty("cursor.mode", "pointer"); },
-             [&]() -> void { fuzzer.addProperty("cursor.mode", "navigation"); },
+            {[&]() -> void { eventHub.addProperty("cursor.mode", "pointer"); },
+             [&]() -> void { eventHub.addProperty("cursor.mode", "navigation"); },
              [&]() -> void {
-                 fuzzer.addProperty("cursor.mode", fdp->ConsumeRandomLengthString(100).data());
+                 eventHub.addProperty("cursor.mode", fdp->ConsumeRandomLengthString(100).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("cursor.orientationAware",
-                                    fdp->ConsumeRandomLengthString(100).data());
+                 eventHub.addProperty("cursor.orientationAware",
+                                      fdp->ConsumeRandomLengthString(100).data());
              }})();
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
     std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
-    FuzzContainer fuzzer(fdp);
 
-    auto policyConfig = fuzzer.getPolicyConfig();
-    CursorInputMapper& mapper = fuzzer.getMapper<CursorInputMapper>(policyConfig);
+    // Create mocked objects to support the fuzzed input mapper.
+    std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+    FuzzInputReaderContext context(eventHub, fdp);
+    InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+    InputReaderConfiguration policyConfig;
+    CursorInputMapper& mapper =
+            getMapperForDevice<ThreadSafeFuzzedDataProvider, CursorInputMapper>(*fdp.get(), device,
+                                                                                policyConfig);
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
         fdp->PickValueInArray<std::function<void()>>({
-                [&]() -> void { addProperty(fuzzer, fdp); },
+                [&]() -> void {
+                    addProperty(*eventHub.get(), fdp);
+                    configureAndResetDevice(*fdp, device);
+                },
                 [&]() -> void {
                     std::string dump;
                     mapper.dump(dump);
@@ -65,22 +76,11 @@
                     mapper.populateDeviceInfo(info);
                 },
                 [&]() -> void {
-                    int32_t type, code;
-                    type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes)
-                                              : fdp->ConsumeIntegral<int32_t>();
-                    code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes)
-                                              : fdp->ConsumeIntegral<int32_t>();
-
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
                             mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                InputReaderConfiguration::Change(0));
-                    RawEvent rawEvent{fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<int32_t>(),
-                                      type,
-                                      code,
-                                      fdp->ConsumeIntegral<int32_t>()};
+                    RawEvent rawEvent = getFuzzedRawEvent(*fdp);
                     unused += mapper.process(&rawEvent);
                 },
                 [&]() -> void {
diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h
deleted file mode 100644
index b992928..0000000
--- a/services/inputflinger/tests/fuzzers/FuzzContainer.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <InputDevice.h>
-#include <InputMapper.h>
-#include <InputReader.h>
-#include <MapperHelpers.h>
-
-namespace android {
-
-class FuzzContainer {
-    std::shared_ptr<FuzzEventHub> mFuzzEventHub;
-    sp<FuzzInputReaderPolicy> mFuzzPolicy;
-    FuzzInputListener mFuzzListener;
-    std::unique_ptr<FuzzInputReaderContext> mFuzzContext;
-    std::unique_ptr<InputDevice> mFuzzDevice;
-    InputReaderConfiguration mPolicyConfig;
-    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
-
-public:
-    FuzzContainer(std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) : mFdp(fdp) {
-        // Setup parameters.
-        std::string deviceName = mFdp->ConsumeRandomLengthString(16);
-        std::string deviceLocation = mFdp->ConsumeRandomLengthString(12);
-        int32_t deviceID = mFdp->ConsumeIntegralInRange<int32_t>(0, 5);
-        int32_t deviceGeneration = mFdp->ConsumeIntegralInRange<int32_t>(/*from*/ 0, /*to*/ 5);
-
-        // Create mocked objects.
-        mFuzzEventHub = std::make_shared<FuzzEventHub>(mFdp);
-        mFuzzPolicy = sp<FuzzInputReaderPolicy>::make(mFdp);
-        mFuzzContext = std::make_unique<FuzzInputReaderContext>(mFuzzEventHub, mFuzzPolicy,
-                                                                mFuzzListener, mFdp);
-
-        InputDeviceIdentifier identifier;
-        identifier.name = deviceName;
-        identifier.location = deviceLocation;
-        mFuzzDevice = std::make_unique<InputDevice>(mFuzzContext.get(), deviceID, deviceGeneration,
-                                                    identifier);
-        mFuzzPolicy->getReaderConfiguration(&mPolicyConfig);
-    }
-
-    ~FuzzContainer() {}
-
-    void configureDevice() {
-        nsecs_t arbitraryTime = mFdp->ConsumeIntegral<nsecs_t>();
-        std::list<NotifyArgs> out;
-        out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, /*changes=*/{});
-        out += mFuzzDevice->reset(arbitraryTime);
-        for (const NotifyArgs& args : out) {
-            mFuzzListener.notify(args);
-        }
-    }
-
-    void addProperty(std::string key, std::string value) {
-        mFuzzEventHub->addProperty(key, value);
-        configureDevice();
-    }
-
-    InputReaderConfiguration& getPolicyConfig() { return mPolicyConfig; }
-
-    template <class T, typename... Args>
-    T& getMapper(Args... args) {
-        int32_t eventhubId = mFdp->ConsumeIntegral<int32_t>();
-        // ensure a device entry exists for this eventHubId
-        mFuzzDevice->addEmptyEventHubDevice(eventhubId);
-        configureDevice();
-
-        return mFuzzDevice->template constructAndAddMapper<T>(eventhubId, args...);
-    }
-};
-
-} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index f8ebc97..3b3ed9b 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -73,9 +73,11 @@
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyKeyArgs
-                    const nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
-                    const nsecs_t readTime =
-                            eventTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
+                    const nsecs_t eventTime =
+                            fdp.ConsumeIntegralInRange<nsecs_t>(0,
+                                                                systemTime(SYSTEM_TIME_MONOTONIC));
+                    const nsecs_t readTime = fdp.ConsumeIntegralInRange<
+                            nsecs_t>(eventTime, std::numeric_limits<nsecs_t>::max());
                     mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
                                             eventTime, readTime,
                                             /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index 616e870..922cbdf 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -14,45 +14,52 @@
  * limitations under the License.
  */
 
-#include <FuzzContainer.h>
+#include <InputDevice.h>
+#include <InputReaderBase.h>
 #include <KeyboardInputMapper.h>
+#include <MapperHelpers.h>
 
 namespace android {
 
 const int32_t kMaxKeycodes = 100;
 
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
+static void addProperty(FuzzEventHub& eventHub, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
     // Pick a random property to set for the mapper to have set.
     fdp->PickValueInArray<std::function<void()>>(
-            {[&]() -> void { fuzzer.addProperty("keyboard.orientationAware", "1"); },
+            {[&]() -> void { eventHub.addProperty("keyboard.orientationAware", "1"); },
              [&]() -> void {
-                 fuzzer.addProperty("keyboard.orientationAware",
-                                    fdp->ConsumeRandomLengthString(100).data());
+                 eventHub.addProperty("keyboard.orientationAware",
+                                      fdp->ConsumeRandomLengthString(100).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("keyboard.doNotWakeByDefault",
-                                    fdp->ConsumeRandomLengthString(100).data());
+                 eventHub.addProperty("keyboard.doNotWakeByDefault",
+                                      fdp->ConsumeRandomLengthString(100).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("keyboard.handlesKeyRepeat",
-                                    fdp->ConsumeRandomLengthString(100).data());
+                 eventHub.addProperty("keyboard.handlesKeyRepeat",
+                                      fdp->ConsumeRandomLengthString(100).data());
              }})();
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
     std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
-    FuzzContainer fuzzer(fdp);
 
-    auto policyConfig = fuzzer.getPolicyConfig();
-    KeyboardInputMapper& mapper =
-            fuzzer.getMapper<KeyboardInputMapper>(policyConfig, fdp->ConsumeIntegral<uint32_t>(),
-                                                  fdp->ConsumeIntegral<int32_t>());
+    // Create mocked objects to support the fuzzed input mapper.
+    std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+    FuzzInputReaderContext context(eventHub, fdp);
+    InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+    KeyboardInputMapper& mapper = getMapperForDevice<
+            ThreadSafeFuzzedDataProvider,
+            KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{},
+                                 /*source=*/fdp->ConsumeIntegral<uint32_t>(),
+                                 /*keyboardType=*/fdp->ConsumeIntegral<int32_t>());
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
         fdp->PickValueInArray<std::function<void()>>({
-                [&]() -> void { addProperty(fuzzer, fdp); },
+                [&]() -> void { addProperty(*eventHub.get(), fdp); },
                 [&]() -> void {
                     std::string dump;
                     mapper.dump(dump);
@@ -64,7 +71,7 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), /*readerConfig=*/{},
                                                InputReaderConfiguration::Change(
                                                        fdp->ConsumeIntegral<uint32_t>()));
                 },
@@ -72,17 +79,7 @@
                     std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
                 },
                 [&]() -> void {
-                    int32_t type, code;
-                    type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes)
-                                              : fdp->ConsumeIntegral<int32_t>();
-                    code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes)
-                                              : fdp->ConsumeIntegral<int32_t>();
-                    RawEvent rawEvent{fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<int32_t>(),
-                                      type,
-                                      code,
-                                      fdp->ConsumeIntegral<int32_t>()};
+                    RawEvent rawEvent = getFuzzedRawEvent(*fdp);
                     std::list<NotifyArgs> unused = mapper.process(&rawEvent);
                 },
                 [&]() -> void {
diff --git a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
index 72780fb..6daeaaf 100644
--- a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
@@ -15,6 +15,9 @@
  */
 
 #include <fuzzer/FuzzedDataProvider.h>
+#include <linux/input.h>
+
+#include "../../InputDeviceMetricsSource.h"
 #include "dispatcher/LatencyTracker.h"
 
 namespace android {
@@ -65,7 +68,11 @@
                     int32_t isDown = fdp.ConsumeBool();
                     nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
                     nsecs_t readTime = fdp.ConsumeIntegral<nsecs_t>();
-                    tracker.trackListener(inputEventId, isDown, eventTime, readTime);
+                    const DeviceId deviceId = fdp.ConsumeIntegral<int32_t>();
+                    std::set<InputDeviceUsageSource> sources = {
+                            fdp.ConsumeEnum<InputDeviceUsageSource>()};
+                    tracker.trackListener(inputEventId, isDown, eventTime, readTime, deviceId,
+                                          sources);
                 },
                 [&]() -> void {
                     int32_t inputEventId = fdp.ConsumeIntegral<int32_t>();
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 1ecaa64..bdedfdf 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -15,6 +15,10 @@
  */
 #pragma once
 
+#include <map>
+#include <memory>
+
+#include <EventHub.h>
 #include <InputDevice.h>
 #include <InputMapper.h>
 #include <InputReader.h>
@@ -22,7 +26,6 @@
 
 constexpr size_t kValidTypes[] = {EV_SW,
                                   EV_SYN,
-                                  SYN_REPORT,
                                   EV_ABS,
                                   EV_KEY,
                                   EV_MSC,
@@ -46,7 +49,6 @@
         ABS_MT_PRESSURE,
         ABS_MT_DISTANCE,
         ABS_MT_TOOL_TYPE,
-        SYN_MT_REPORT,
         MSC_SCAN,
         REL_X,
         REL_Y,
@@ -74,10 +76,27 @@
     return static_cast<ToolType>(toolType);
 }
 
+template <class Fdp>
+RawEvent getFuzzedRawEvent(Fdp& fdp) {
+    const int32_t type = fdp.ConsumeBool() ? fdp.PickValueInArray(kValidTypes)
+                                           : fdp.template ConsumeIntegral<int32_t>();
+    const int32_t code = fdp.ConsumeBool() ? fdp.PickValueInArray(kValidCodes)
+                                           : fdp.template ConsumeIntegral<int32_t>();
+    return RawEvent{
+            .when = fdp.template ConsumeIntegral<nsecs_t>(),
+            .readTime = fdp.template ConsumeIntegral<nsecs_t>(),
+            .deviceId = fdp.template ConsumeIntegral<int32_t>(),
+            .type = type,
+            .code = code,
+            .value = fdp.template ConsumeIntegral<int32_t>(),
+    };
+}
+
 class FuzzEventHub : public EventHubInterface {
     InputDeviceIdentifier mIdentifier;
     std::vector<TouchVideoFrame> mVideoFrames;
     PropertyMap mFuzzConfig;
+    std::map<int32_t /* deviceId */, std::map<int /* axis */, RawAbsoluteAxisInfo>> mAxes;
     std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 public:
@@ -97,8 +116,18 @@
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override {
         return mFuzzConfig;
     }
+    void setAbsoluteAxisInfo(int32_t deviceId, int axis, const RawAbsoluteAxisInfo& axisInfo) {
+        mAxes[deviceId][axis] = axisInfo;
+    }
     status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
                                  RawAbsoluteAxisInfo* outAxisInfo) const override {
+        if (auto deviceAxesIt = mAxes.find(deviceId); deviceAxesIt != mAxes.end()) {
+            const std::map<int, RawAbsoluteAxisInfo>& deviceAxes = deviceAxesIt->second;
+            if (auto axisInfoIt = deviceAxes.find(axis); axisInfoIt != deviceAxes.end()) {
+                *outAxisInfo = axisInfoIt->second;
+                return OK;
+            }
+        }
         return mFdp->ConsumeIntegral<status_t>();
     }
     bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); }
@@ -118,18 +147,7 @@
         std::vector<RawEvent> events;
         const size_t count = mFdp->ConsumeIntegralInRange<size_t>(0, kMaxSize);
         for (size_t i = 0; i < count; ++i) {
-            int32_t type = mFdp->ConsumeBool() ? mFdp->PickValueInArray(kValidTypes)
-                                               : mFdp->ConsumeIntegral<int32_t>();
-            int32_t code = mFdp->ConsumeBool() ? mFdp->PickValueInArray(kValidCodes)
-                                               : mFdp->ConsumeIntegral<int32_t>();
-            events.push_back({
-                    .when = mFdp->ConsumeIntegral<nsecs_t>(),
-                    .readTime = mFdp->ConsumeIntegral<nsecs_t>(),
-                    .deviceId = mFdp->ConsumeIntegral<int32_t>(),
-                    .type = type,
-                    .code = code,
-                    .value = mFdp->ConsumeIntegral<int32_t>(),
-            });
+            events.push_back(getFuzzedRawEvent(*mFdp));
         }
         return events;
     }
@@ -277,7 +295,8 @@
     }
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
-            const InputDeviceIdentifier& identifier) override {
+            const InputDeviceIdentifier& identifier,
+            const std::optional<KeyboardLayoutInfo> layoutInfo) override {
         return nullptr;
     }
     std::string getDeviceAlias(const InputDeviceIdentifier& identifier) {
@@ -312,10 +331,8 @@
 
 public:
     FuzzInputReaderContext(std::shared_ptr<EventHubInterface> eventHub,
-                           const sp<InputReaderPolicyInterface>& policy,
-                           InputListenerInterface& listener,
-                           std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp)
-          : mEventHub(eventHub), mPolicy(policy), mFdp(mFdp) {}
+                           std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp)
+          : mEventHub(eventHub), mPolicy(sp<FuzzInputReaderPolicy>::make(fdp)), mFdp(fdp) {}
     ~FuzzInputReaderContext() {}
     void updateGlobalMetaState() override {}
     int32_t getGlobalMetaState() { return mFdp->ConsumeIntegral<int32_t>(); }
@@ -345,4 +362,32 @@
     bool isPreventingTouchpadTaps() { return mFdp->ConsumeBool(); };
 };
 
+template <class Fdp>
+InputDevice getFuzzedInputDevice(Fdp& fdp, FuzzInputReaderContext* context) {
+    InputDeviceIdentifier identifier;
+    identifier.name = fdp.ConsumeRandomLengthString(16);
+    identifier.location = fdp.ConsumeRandomLengthString(12);
+    int32_t deviceID = fdp.ConsumeIntegralInRange(0, 5);
+    int32_t deviceGeneration = fdp.ConsumeIntegralInRange(0, 5);
+    return InputDevice(context, deviceID, deviceGeneration, identifier);
+}
+
+template <class Fdp>
+void configureAndResetDevice(Fdp& fdp, InputDevice& device) {
+    nsecs_t arbitraryTime = fdp.template ConsumeIntegral<nsecs_t>();
+    std::list<NotifyArgs> out;
+    out += device.configure(arbitraryTime, /*readerConfig=*/{}, /*changes=*/{});
+    out += device.reset(arbitraryTime);
+}
+
+template <class Fdp, class T, typename... Args>
+T& getMapperForDevice(Fdp& fdp, InputDevice& device, Args... args) {
+    int32_t eventhubId = fdp.template ConsumeIntegral<int32_t>();
+    // ensure a device entry exists for this eventHubId
+    device.addEmptyEventHubDevice(eventhubId);
+    configureAndResetDevice(fdp, device);
+
+    return device.template constructAndAddMapper<T>(eventhubId, args...);
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index 212462d..d3f6690 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -14,60 +14,72 @@
  * limitations under the License.
  */
 
-#include <FuzzContainer.h>
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
 #include <MultiTouchInputMapper.h>
 
 namespace android {
 
 const int32_t kMaxKeycodes = 100;
 
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
+static void addProperty(FuzzEventHub& eventHub, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
     // Pick a random property to set for the mapper to have set.
     fdp->PickValueInArray<std::function<void()>>(
-            {[&]() -> void { fuzzer.addProperty("touch.deviceType", "touchScreen"); },
+            {[&]() -> void { eventHub.addProperty("touch.deviceType", "touchScreen"); },
              [&]() -> void {
-                 fuzzer.addProperty("touch.deviceType", fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.deviceType", fdp->ConsumeRandomLengthString(8).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.size.scale", fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.size.scale", fdp->ConsumeRandomLengthString(8).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.size.bias", fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.size.bias", fdp->ConsumeRandomLengthString(8).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.size.isSummed",
-                                    fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.size.isSummed",
+                                      fdp->ConsumeRandomLengthString(8).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.size.calibration",
-                                    fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.size.calibration",
+                                      fdp->ConsumeRandomLengthString(8).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.pressure.scale",
-                                    fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.pressure.scale",
+                                      fdp->ConsumeRandomLengthString(8).data());
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.size.calibration",
-                                    fdp->ConsumeBool() ? "diameter" : "area");
+                 eventHub.addProperty("touch.size.calibration",
+                                      fdp->ConsumeBool() ? "diameter" : "area");
              },
              [&]() -> void {
-                 fuzzer.addProperty("touch.pressure.calibration",
-                                    fdp->ConsumeRandomLengthString(8).data());
+                 eventHub.addProperty("touch.pressure.calibration",
+                                      fdp->ConsumeRandomLengthString(8).data());
              }})();
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
     std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
-    FuzzContainer fuzzer(fdp);
 
-    auto policyConfig = fuzzer.getPolicyConfig();
-    MultiTouchInputMapper& mapper = fuzzer.getMapper<MultiTouchInputMapper>(policyConfig);
+    // Create mocked objects to support the fuzzed input mapper.
+    std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+    FuzzInputReaderContext context(eventHub, fdp);
+    InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+    InputReaderConfiguration policyConfig;
+    MultiTouchInputMapper& mapper =
+            getMapperForDevice<ThreadSafeFuzzedDataProvider, MultiTouchInputMapper>(*fdp.get(),
+                                                                                    device,
+                                                                                    policyConfig);
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
         fdp->PickValueInArray<std::function<void()>>({
-                [&]() -> void { addProperty(fuzzer, fdp); },
+                [&]() -> void {
+                    addProperty(*eventHub.get(), fdp);
+                    configureAndResetDevice(*fdp, device);
+                },
                 [&]() -> void {
                     std::string dump;
                     mapper.dump(dump);
@@ -87,16 +99,7 @@
                     std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
                 },
                 [&]() -> void {
-                    int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes)
-                                                      : fdp->ConsumeIntegral<int32_t>();
-                    int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes)
-                                                      : fdp->ConsumeIntegral<int32_t>();
-                    RawEvent rawEvent{fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<int32_t>(),
-                                      type,
-                                      code,
-                                      fdp->ConsumeIntegral<int32_t>()};
+                    RawEvent rawEvent = getFuzzedRawEvent(*fdp);
                     std::list<NotifyArgs> unused = mapper.process(&rawEvent);
                 },
                 [&]() -> void {
diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
index 590207e..ac2030a 100644
--- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-#include <FuzzContainer.h>
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
 #include <SwitchInputMapper.h>
 
 namespace android {
@@ -22,10 +24,15 @@
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
     std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
             std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
-    FuzzContainer fuzzer(fdp);
 
-    auto policyConfig = fuzzer.getPolicyConfig();
-    SwitchInputMapper& mapper = fuzzer.getMapper<SwitchInputMapper>(policyConfig);
+    // Create mocked objects to support the fuzzed input mapper.
+    std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+    FuzzInputReaderContext context(eventHub, fdp);
+    InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+    SwitchInputMapper& mapper =
+            getMapperForDevice<ThreadSafeFuzzedDataProvider,
+                               SwitchInputMapper>(*fdp.get(), device, InputReaderConfiguration{});
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
@@ -36,16 +43,7 @@
                 },
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
-                    int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes)
-                                                      : fdp->ConsumeIntegral<int32_t>();
-                    int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes)
-                                                      : fdp->ConsumeIntegral<int32_t>();
-                    RawEvent rawEvent{fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<nsecs_t>(),
-                                      fdp->ConsumeIntegral<int32_t>(),
-                                      type,
-                                      code,
-                                      fdp->ConsumeIntegral<int32_t>()};
+                    RawEvent rawEvent = getFuzzedRawEvent(*fdp);
                     std::list<NotifyArgs> unused = mapper.process(&rawEvent);
                 },
                 [&]() -> void {
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
new file mode 100644
index 0000000..be765cc
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <linux/input-event-codes.h>
+
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
+#include <TouchpadInputMapper.h>
+
+namespace android {
+
+namespace {
+
+void setAxisInfo(ThreadSafeFuzzedDataProvider& fdp, FuzzEventHub& eventHub, int32_t id, int axis) {
+    if (fdp.ConsumeBool()) {
+        eventHub.setAbsoluteAxisInfo(id, axis,
+                                     RawAbsoluteAxisInfo{
+                                             .valid = fdp.ConsumeBool(),
+                                             .minValue = fdp.ConsumeIntegral<int32_t>(),
+                                             .maxValue = fdp.ConsumeIntegral<int32_t>(),
+                                             .flat = fdp.ConsumeIntegral<int32_t>(),
+                                             .fuzz = fdp.ConsumeIntegral<int32_t>(),
+                                             .resolution = fdp.ConsumeIntegral<int32_t>(),
+                                     });
+    }
+}
+
+void setAxisInfos(ThreadSafeFuzzedDataProvider& fdp, FuzzEventHub& eventHub, int32_t id) {
+    setAxisInfo(fdp, eventHub, id, ABS_MT_SLOT);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_POSITION_X);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_POSITION_Y);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_PRESSURE);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_ORIENTATION);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_TOUCH_MAJOR);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_TOUCH_MINOR);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_WIDTH_MAJOR);
+    setAxisInfo(fdp, eventHub, id, ABS_MT_WIDTH_MINOR);
+}
+
+const std::vector<std::string> boolPropertiesToFuzz = {
+        "gestureProp.Compute_Surface_Area_from_Pressure",
+        "gestureProp.Drumroll_Suppression_Enable",
+        "gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls",
+        "gestureProp.Stationary_Wiggle_Filter_Enabled",
+};
+const std::vector<std::string> doublePropertiesToFuzz = {
+        "gestureProp.Fake_Timestamp_Delta",
+        "gestureProp.Finger_Moving_Energy",
+        "gestureProp.Finger_Moving_Hysteresis",
+        "gestureProp.IIR_a1",
+        "gestureProp.IIR_a2",
+        "gestureProp.IIR_b0",
+        "gestureProp.IIR_b1",
+        "gestureProp.IIR_b2",
+        "gestureProp.IIR_b3",
+        "gestureProp.Max_Allowed_Pressure_Change_Per_Sec",
+        "gestureProp.Max_Hysteresis_Pressure_Per_Sec",
+        "gestureProp.Max_Stationary_Move_Speed",
+        "gestureProp.Max_Stationary_Move_Speed_Hysteresis",
+        "gestureProp.Max_Stationary_Move_Suppress_Distance",
+        "gestureProp.Multiple_Palm_Width",
+        "gestureProp.Palm_Edge_Zone_Width",
+        "gestureProp.Palm_Eval_Timeout",
+        "gestureProp.Palm_Pressure",
+        "gestureProp.Palm_Width",
+        "gestureProp.Pressure_Calibration_Offset",
+        "gestureProp.Pressure_Calibration_Slope",
+        "gestureProp.Tap_Exclusion_Border_Width",
+        "gestureProp.Touchpad_Device_Output_Bias_on_X-Axis",
+        "gestureProp.Touchpad_Device_Output_Bias_on_Y-Axis",
+        "gestureProp.Two_Finger_Vertical_Close_Distance_Thresh",
+};
+
+void setDeviceSpecificConfig(ThreadSafeFuzzedDataProvider& fdp, FuzzEventHub& eventHub) {
+    // There are a great many gesture properties offered by the Gestures library, all of which could
+    // potentially be set in Input Device Configuration files. Maintaining a complete list is
+    // impractical, so instead we only fuzz properties which are used in at least one IDC file, or
+    // which are likely to be used in future (e.g. ones for controlling palm rejection).
+
+    if (fdp.ConsumeBool()) {
+        eventHub.addProperty("gestureProp.Touchpad_Stack_Version",
+                             std::to_string(fdp.ConsumeIntegral<int>()));
+    }
+
+    for (auto& propertyName : boolPropertiesToFuzz) {
+        if (fdp.ConsumeBool()) {
+            eventHub.addProperty(propertyName, fdp.ConsumeBool() ? "1" : "0");
+        }
+    }
+
+    for (auto& propertyName : doublePropertiesToFuzz) {
+        if (fdp.ConsumeBool()) {
+            eventHub.addProperty(propertyName, std::to_string(fdp.ConsumeFloatingPoint<double>()));
+        }
+    }
+
+    if (fdp.ConsumeBool()) {
+        eventHub.addProperty("gestureProp." + fdp.ConsumeRandomLengthString(),
+                             std::to_string(fdp.ConsumeIntegral<int>()));
+    }
+}
+
+void setTouchpadSettings(ThreadSafeFuzzedDataProvider& fdp, InputReaderConfiguration& config) {
+    config.touchpadPointerSpeed = fdp.ConsumeIntegralInRange(-7, 7);
+    config.touchpadNaturalScrollingEnabled = fdp.ConsumeBool();
+    config.touchpadTapToClickEnabled = fdp.ConsumeBool();
+    config.touchpadRightClickZoneEnabled = fdp.ConsumeBool();
+}
+
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+            std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+    // Create mocked objects to support the fuzzed input mapper.
+    std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+    FuzzInputReaderContext context(eventHub, fdp);
+    InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+    setAxisInfos(*fdp, *eventHub.get(), device.getId());
+    setDeviceSpecificConfig(*fdp, *eventHub.get());
+
+    InputReaderConfiguration policyConfig;
+    // Some settings are fuzzed here, as well as in the main loop, to provide randomized data to the
+    // TouchpadInputMapper constructor.
+    setTouchpadSettings(*fdp, policyConfig);
+    policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
+    TouchpadInputMapper& mapper =
+            getMapperForDevice<ThreadSafeFuzzedDataProvider, TouchpadInputMapper>(*fdp, device,
+                                                                                  policyConfig);
+
+    // Loop through mapper operations until randomness is exhausted.
+    while (fdp->remaining_bytes() > 0) {
+        fdp->PickValueInArray<std::function<void()>>({
+                [&]() -> void {
+                    std::string dump;
+                    mapper.dump(dump);
+                },
+                [&]() -> void {
+                    InputDeviceInfo info;
+                    mapper.populateDeviceInfo(info);
+                },
+                [&]() -> void { mapper.getSources(); },
+                [&]() -> void {
+                    setTouchpadSettings(*fdp, policyConfig);
+                    policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
+                    std::list<NotifyArgs> unused =
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+                                               InputReaderConfiguration::Change(
+                                                       fdp->ConsumeIntegral<uint32_t>()));
+                },
+                [&]() -> void {
+                    std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
+                },
+                [&]() -> void {
+                    RawEvent event = getFuzzedRawEvent(*fdp);
+                    std::list<NotifyArgs> unused = mapper.process(&event);
+                },
+        })();
+    }
+
+    return 0;
+}
+
+} // namespace android
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index b34e54f..8b16890 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -9,7 +9,7 @@
 
 cc_library_shared {
     name: "libpowermanager",
-
+    defaults: ["android.hardware.power-ndk_export_shared"],
     srcs: [
         "BatterySaverPolicyConfig.cpp",
         "CoolingDevice.cpp",
@@ -33,6 +33,7 @@
 
     shared_libs: [
         "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libutils",
@@ -40,7 +41,6 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
     ],
 
     export_shared_lib_headers: [
@@ -48,7 +48,6 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
     ],
 
     cflags: [
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index f89035f..9a23c84 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -15,11 +15,11 @@
  */
 
 #define LOG_TAG "PowerHalController"
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
 #include <powermanager/PowerHalController.h>
 #include <powermanager/PowerHalLoader.h>
 #include <utils/Log.h>
@@ -33,7 +33,8 @@
 // -------------------------------------------------------------------------------------------------
 
 std::unique_ptr<HalWrapper> HalConnector::connect() {
-    if (sp<IPower> halAidl = PowerHalLoader::loadAidl()) {
+    if (std::shared_ptr<aidl::android::hardware::power::IPower> halAidl =
+                PowerHalLoader::loadAidl()) {
         return std::make_unique<AidlHalWrapper>(halAidl);
     }
     // If V1_0 isn't defined, none of them are
@@ -90,20 +91,24 @@
     return result;
 }
 
-HalResult<void> PowerHalController::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> PowerHalController::setBoost(aidl::android::hardware::power::Boost boost,
+                                             int32_t durationMs) {
     std::shared_ptr<HalWrapper> handle = initHal();
     auto result = handle->setBoost(boost, durationMs);
     return processHalResult(result, "setBoost");
 }
 
-HalResult<void> PowerHalController::setMode(Mode mode, bool enabled) {
+HalResult<void> PowerHalController::setMode(aidl::android::hardware::power::Mode mode,
+                                            bool enabled) {
     std::shared_ptr<HalWrapper> handle = initHal();
     auto result = handle->setMode(mode, enabled);
     return processHalResult(result, "setMode");
 }
 
-HalResult<sp<IPowerHintSession>> PowerHalController::createHintSession(
-        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
+HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+PowerHalController::createHintSession(int32_t tgid, int32_t uid,
+                                      const std::vector<int32_t>& threadIds,
+                                      int64_t durationNanos) {
     std::shared_ptr<HalWrapper> handle = initHal();
     auto result = handle->createHintSession(tgid, uid, threadIds, durationNanos);
     return processHalResult(result, "createHintSession");
diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp
index 6bd40f8..2214461 100644
--- a/services/powermanager/PowerHalLoader.cpp
+++ b/services/powermanager/PowerHalLoader.cpp
@@ -16,10 +16,11 @@
 
 #define LOG_TAG "PowerHalLoader"
 
+#include <aidl/android/hardware/power/IPower.h>
+#include <android/binder_manager.h>
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
-#include <android/hardware/power/IPower.h>
 #include <binder/IServiceManager.h>
 #include <hardware/power.h>
 #include <hardware_legacy/power.h>
@@ -54,7 +55,7 @@
 // -------------------------------------------------------------------------------------------------
 
 std::mutex PowerHalLoader::gHalMutex;
-sp<IPower> PowerHalLoader::gHalAidl = nullptr;
+std::shared_ptr<aidl::android::hardware::power::IPower> PowerHalLoader::gHalAidl = nullptr;
 sp<V1_0::IPower> PowerHalLoader::gHalHidlV1_0 = nullptr;
 sp<V1_1::IPower> PowerHalLoader::gHalHidlV1_1 = nullptr;
 sp<V1_2::IPower> PowerHalLoader::gHalHidlV1_2 = nullptr;
@@ -69,11 +70,30 @@
     gHalHidlV1_3 = nullptr;
 }
 
-sp<IPower> PowerHalLoader::loadAidl() {
+std::shared_ptr<aidl::android::hardware::power::IPower> PowerHalLoader::loadAidl() {
     std::lock_guard<std::mutex> lock(gHalMutex);
     static bool gHalExists = true;
-    static auto loadFn = []() { return waitForVintfService<IPower>(); };
-    return loadHal<IPower>(gHalExists, gHalAidl, loadFn, "AIDL");
+    if (!gHalExists) {
+        return nullptr;
+    }
+    if (gHalAidl) {
+        return gHalAidl;
+    }
+    auto aidlServiceName =
+            std::string(aidl::android::hardware::power::IPower::descriptor) + "/default";
+    if (!AServiceManager_isDeclared(aidlServiceName.c_str())) {
+        gHalExists = false;
+        return nullptr;
+    }
+    gHalAidl = aidl::android::hardware::power::IPower::fromBinder(
+            ndk::SpAIBinder(AServiceManager_waitForService(aidlServiceName.c_str())));
+    if (gHalAidl) {
+        ALOGI("Successfully connected to Power HAL AIDL service.");
+    } else {
+        ALOGI("Power HAL AIDL service not available.");
+        gHalExists = false;
+    }
+    return gHalAidl;
 }
 
 sp<V1_0::IPower> PowerHalLoader::loadHidlV1_0() {
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index 9e7adf8..76afbfc 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -15,86 +15,49 @@
  */
 
 #define LOG_TAG "HalWrapper"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
 #include <cinttypes>
 
 using namespace android::hardware::power;
-namespace Aidl = android::hardware::power;
+namespace Aidl = aidl::android::hardware::power;
 
 namespace android {
 
 namespace power {
 
 // -------------------------------------------------------------------------------------------------
-
-inline HalResult<void> toHalResult(const binder::Status& result) {
+inline HalResult<void> toHalResult(const ndk::ScopedAStatus& result) {
     if (result.isOk()) {
         return HalResult<void>::ok();
     }
-    ALOGE("Power HAL request failed: %s", result.toString8().c_str());
-    return HalResult<void>::fromStatus(result);
-}
-
-template <typename T>
-template <typename R>
-HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, T data) {
-    return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
-}
-
-template <typename T>
-template <typename R>
-HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, V1_0::Status status, T data) {
-    return ret.isOk() ? HalResult<T>::fromStatus(status, data)
-                      : HalResult<T>::failed(ret.description());
+    ALOGE("Power HAL request failed: %s", result.getDescription().c_str());
+    return HalResult<void>::failed(result.getDescription());
 }
 
 // -------------------------------------------------------------------------------------------------
 
-HalResult<void> HalResult<void>::fromStatus(status_t status) {
-    if (status == android::OK) {
-        return HalResult<void>::ok();
-    }
-    return HalResult<void>::failed(statusToString(status));
-}
-
-HalResult<void> HalResult<void>::fromStatus(binder::Status status) {
-    if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-        return HalResult<void>::unsupported();
-    }
-    if (status.isOk()) {
-        return HalResult<void>::ok();
-    }
-    return HalResult<void>::failed(std::string(status.toString8().c_str()));
-}
-
-template <typename R>
-HalResult<void> HalResult<void>::fromReturn(hardware::Return<R>& ret) {
-    return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
-}
-// -------------------------------------------------------------------------------------------------
-
-HalResult<void> EmptyHalWrapper::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
     ALOGV("Skipped setBoost %s with duration %dms because Power HAL not available",
           toString(boost).c_str(), durationMs);
     return HalResult<void>::unsupported();
 }
 
-HalResult<void> EmptyHalWrapper::setMode(Mode mode, bool enabled) {
+HalResult<void> EmptyHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
     ALOGV("Skipped setMode %s to %s because Power HAL not available", toString(mode).c_str(),
           enabled ? "true" : "false");
     return HalResult<void>::unsupported();
 }
 
-HalResult<sp<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
     ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
           threadIds.size());
-    return HalResult<sp<Aidl::IPowerHintSession>>::unsupported();
+    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
 }
 
 HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
@@ -104,8 +67,8 @@
 
 // -------------------------------------------------------------------------------------------------
 
-HalResult<void> HidlHalWrapperV1_0::setBoost(Boost boost, int32_t durationMs) {
-    if (boost == Boost::INTERACTION) {
+HalResult<void> HidlHalWrapperV1_0::setBoost(Aidl::Boost boost, int32_t durationMs) {
+    if (boost == Aidl::Boost::INTERACTION) {
         return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs);
     } else {
         ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str());
@@ -113,20 +76,20 @@
     }
 }
 
-HalResult<void> HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) {
+HalResult<void> HidlHalWrapperV1_0::setMode(Aidl::Mode mode, bool enabled) {
     uint32_t data = enabled ? 1 : 0;
     switch (mode) {
-        case Mode::LAUNCH:
+        case Aidl::Mode::LAUNCH:
             return sendPowerHint(V1_3::PowerHint::LAUNCH, data);
-        case Mode::LOW_POWER:
+        case Aidl::Mode::LOW_POWER:
             return sendPowerHint(V1_3::PowerHint::LOW_POWER, data);
-        case Mode::SUSTAINED_PERFORMANCE:
+        case Aidl::Mode::SUSTAINED_PERFORMANCE:
             return sendPowerHint(V1_3::PowerHint::SUSTAINED_PERFORMANCE, data);
-        case Mode::VR:
+        case Aidl::Mode::VR:
             return sendPowerHint(V1_3::PowerHint::VR_MODE, data);
-        case Mode::INTERACTIVE:
+        case Aidl::Mode::INTERACTIVE:
             return setInteractive(enabled);
-        case Mode::DOUBLE_TAP_TO_WAKE:
+        case Aidl::Mode::DOUBLE_TAP_TO_WAKE:
             return setFeature(V1_0::Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE, enabled);
         default:
             ALOGV("Skipped setMode %s because Power HAL AIDL not available",
@@ -150,11 +113,11 @@
     return HalResult<void>::fromReturn(ret);
 }
 
-HalResult<sp<hardware::power::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
     ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
           threadIds.size());
-    return HalResult<sp<Aidl::IPowerHintSession>>::unsupported();
+    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
 }
 
 HalResult<int64_t> HidlHalWrapperV1_0::getHintSessionPreferredRate() {
@@ -178,26 +141,26 @@
     return HalResult<void>::fromReturn(ret);
 }
 
-HalResult<void> HidlHalWrapperV1_2::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> HidlHalWrapperV1_2::setBoost(Aidl::Boost boost, int32_t durationMs) {
     switch (boost) {
-        case Boost::CAMERA_SHOT:
+        case Aidl::Boost::CAMERA_SHOT:
             return sendPowerHint(V1_3::PowerHint::CAMERA_SHOT, durationMs);
-        case Boost::CAMERA_LAUNCH:
+        case Aidl::Boost::CAMERA_LAUNCH:
             return sendPowerHint(V1_3::PowerHint::CAMERA_LAUNCH, durationMs);
         default:
             return HidlHalWrapperV1_1::setBoost(boost, durationMs);
     }
 }
 
-HalResult<void> HidlHalWrapperV1_2::setMode(Mode mode, bool enabled) {
+HalResult<void> HidlHalWrapperV1_2::setMode(Aidl::Mode mode, bool enabled) {
     uint32_t data = enabled ? 1 : 0;
     switch (mode) {
-        case Mode::CAMERA_STREAMING_SECURE:
-        case Mode::CAMERA_STREAMING_LOW:
-        case Mode::CAMERA_STREAMING_MID:
-        case Mode::CAMERA_STREAMING_HIGH:
+        case Aidl::Mode::CAMERA_STREAMING_SECURE:
+        case Aidl::Mode::CAMERA_STREAMING_LOW:
+        case Aidl::Mode::CAMERA_STREAMING_MID:
+        case Aidl::Mode::CAMERA_STREAMING_HIGH:
             return sendPowerHint(V1_3::PowerHint::CAMERA_STREAMING, data);
-        case Mode::AUDIO_STREAMING_LOW_LATENCY:
+        case Aidl::Mode::AUDIO_STREAMING_LOW_LATENCY:
             return sendPowerHint(V1_3::PowerHint::AUDIO_LOW_LATENCY, data);
         default:
             return HidlHalWrapperV1_1::setMode(mode, enabled);
@@ -206,9 +169,9 @@
 
 // -------------------------------------------------------------------------------------------------
 
-HalResult<void> HidlHalWrapperV1_3::setMode(Mode mode, bool enabled) {
+HalResult<void> HidlHalWrapperV1_3::setMode(Aidl::Mode mode, bool enabled) {
     uint32_t data = enabled ? 1 : 0;
-    if (mode == Mode::EXPENSIVE_RENDERING) {
+    if (mode == Aidl::Mode::EXPENSIVE_RENDERING) {
         return sendPowerHint(V1_3::PowerHint::EXPENSIVE_RENDERING, data);
     }
     return HidlHalWrapperV1_2::setMode(mode, enabled);
@@ -222,7 +185,7 @@
 
 // -------------------------------------------------------------------------------------------------
 
-HalResult<void> AidlHalWrapper::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> AidlHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
     std::unique_lock<std::mutex> lock(mBoostMutex);
     size_t idx = static_cast<size_t>(boost);
 
@@ -237,7 +200,7 @@
         auto isSupportedRet = mHandle->isBoostSupported(boost, &isSupported);
         if (!isSupportedRet.isOk()) {
             ALOGE("Skipped setBoost %s because check support failed with: %s",
-                  toString(boost).c_str(), isSupportedRet.toString8().c_str());
+                  toString(boost).c_str(), isSupportedRet.getDescription().c_str());
             // return HalResult::FAILED;
             return HalResult<void>::fromStatus(isSupportedRet);
         }
@@ -254,7 +217,7 @@
     return toHalResult(mHandle->setBoost(boost, durationMs));
 }
 
-HalResult<void> AidlHalWrapper::setMode(Mode mode, bool enabled) {
+HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
     std::unique_lock<std::mutex> lock(mModeMutex);
     size_t idx = static_cast<size_t>(mode);
 
@@ -268,7 +231,7 @@
         bool isSupported = false;
         auto isSupportedRet = mHandle->isModeSupported(mode, &isSupported);
         if (!isSupportedRet.isOk()) {
-            return HalResult<void>::failed(isSupportedRet.toString8().c_str());
+            return HalResult<void>::failed(isSupportedRet.getDescription());
         }
 
         mModeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
@@ -283,10 +246,10 @@
     return toHalResult(mHandle->setMode(mode, enabled));
 }
 
-HalResult<sp<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
-    sp<IPowerHintSession> appSession;
-    return HalResult<sp<Aidl::IPowerHintSession>>::
+    std::shared_ptr<Aidl::IPowerHintSession> appSession;
+    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
             fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession),
                        appSession);
 }
diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp
index 4343aec..2b5ddb1 100644
--- a/services/powermanager/benchmarks/Android.bp
+++ b/services/powermanager/benchmarks/Android.bp
@@ -23,6 +23,7 @@
 
 cc_benchmark {
     name: "libpowermanager_benchmarks",
+    defaults: ["android.hardware.power-ndk_shared"],
     srcs: [
         "main.cpp",
         "PowerHalAidlBenchmarks.cpp",
@@ -32,6 +33,7 @@
     shared_libs: [
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libpowermanager",
@@ -40,7 +42,6 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
     ],
     static_libs: [
         "libtestUtil",
diff --git a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
index 6e5e14d..759485f 100644
--- a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
@@ -16,21 +16,24 @@
 
 #define LOG_TAG "PowerHalAidlBenchmarks"
 
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
-#include <android/hardware/power/WorkDuration.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/WorkDuration.h>
 #include <benchmark/benchmark.h>
 #include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <powermanager/PowerHalLoader.h>
 #include <testUtil.h>
 #include <chrono>
 
-using android::hardware::power::Boost;
-using android::hardware::power::IPower;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
-using android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPower;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHalLoader;
 using std::chrono::microseconds;
 
 using namespace android;
@@ -63,15 +66,15 @@
 template <class R, class... Args0, class... Args1>
 static void runBenchmark(benchmark::State& state, microseconds delay, R (IPower::*fn)(Args0...),
                          Args1&&... args1) {
-    sp<IPower> hal = waitForVintfService<IPower>();
+    std::shared_ptr<IPower> hal = PowerHalLoader::loadAidl();
 
     if (hal == nullptr) {
         ALOGV("Power HAL not available, skipping test...");
         return;
     }
 
-    binder::Status ret = (*hal.*fn)(std::forward<Args1>(args1)...);
-    if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
+    ndk::ScopedAStatus ret = (*hal.*fn)(std::forward<Args1>(args1)...);
+    if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
         ALOGV("Power HAL does not support this operation, skipping test...");
         return;
     }
@@ -79,7 +82,7 @@
     while (state.KeepRunning()) {
         ret = (*hal.*fn)(std::forward<Args1>(args1)...);
         state.PauseTiming();
-        if (!ret.isOk()) state.SkipWithError(ret.toString8().c_str());
+        if (!ret.isOk()) state.SkipWithError(ret.getDescription().c_str());
         if (delay > 0us) {
             testDelaySpin(std::chrono::duration_cast<std::chrono::duration<float>>(delay).count());
         }
@@ -90,9 +93,9 @@
 template <class R, class... Args0, class... Args1>
 static void runSessionBenchmark(benchmark::State& state, R (IPowerHintSession::*fn)(Args0...),
                                 Args1&&... args1) {
-    sp<IPower> pwHal = waitForVintfService<IPower>();
+    std::shared_ptr<IPower> hal = PowerHalLoader::loadAidl();
 
-    if (pwHal == nullptr) {
+    if (hal == nullptr) {
         ALOGV("Power HAL not available, skipping test...");
         return;
     }
@@ -100,32 +103,32 @@
     // do not use tid from the benchmark process, use 1 for init
     std::vector<int32_t> threadIds{1};
     int64_t durationNanos = 16666666L;
-    sp<IPowerHintSession> hal;
+    std::shared_ptr<IPowerHintSession> session;
 
-    auto status = pwHal->createHintSession(1, 0, threadIds, durationNanos, &hal);
+    auto status = hal->createHintSession(1, 0, threadIds, durationNanos, &session);
 
-    if (hal == nullptr) {
+    if (session == nullptr) {
         ALOGV("Power HAL doesn't support session, skipping test...");
         return;
     }
 
-    binder::Status ret = (*hal.*fn)(std::forward<Args1>(args1)...);
-    if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
+    ndk::ScopedAStatus ret = (*session.*fn)(std::forward<Args1>(args1)...);
+    if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
         ALOGV("Power HAL does not support this operation, skipping test...");
         return;
     }
 
     while (state.KeepRunning()) {
-        ret = (*hal.*fn)(std::forward<Args1>(args1)...);
+        ret = (*session.*fn)(std::forward<Args1>(args1)...);
         state.PauseTiming();
-        if (!ret.isOk()) state.SkipWithError(ret.toString8().c_str());
+        if (!ret.isOk()) state.SkipWithError(ret.getDescription().c_str());
         if (ONEWAY_API_DELAY > 0us) {
             testDelaySpin(std::chrono::duration_cast<std::chrono::duration<float>>(ONEWAY_API_DELAY)
                                   .count());
         }
         state.ResumeTiming();
     }
-    hal->close();
+    session->close();
 }
 
 static void BM_PowerHalAidlBenchmarks_isBoostSupported(benchmark::State& state) {
@@ -155,16 +158,17 @@
     int64_t durationNanos = 16666666L;
     int32_t tgid = 999;
     int32_t uid = 1001;
-    sp<IPowerHintSession> appSession;
-    sp<IPower> hal = waitForVintfService<IPower>();
+    std::shared_ptr<IPowerHintSession> appSession;
+    std::shared_ptr<IPower> hal = PowerHalLoader::loadAidl();
 
     if (hal == nullptr) {
         ALOGV("Power HAL not available, skipping test...");
         return;
     }
 
-    binder::Status ret = hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
-    if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
+    ndk::ScopedAStatus ret =
+            hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
+    if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
         ALOGV("Power HAL does not support this operation, skipping test...");
         return;
     }
@@ -172,7 +176,7 @@
     while (state.KeepRunning()) {
         ret = hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
         state.PauseTiming();
-        if (!ret.isOk()) state.SkipWithError(ret.toString8().c_str());
+        if (!ret.isOk()) state.SkipWithError(ret.getDescription().c_str());
         appSession->close();
         state.ResumeTiming();
     }
diff --git a/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp
index f8abc7a..effddda 100644
--- a/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp
@@ -16,15 +16,15 @@
 
 #define LOG_TAG "PowerHalControllerBenchmarks"
 
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <benchmark/benchmark.h>
 #include <powermanager/PowerHalController.h>
 #include <testUtil.h>
 #include <chrono>
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::power::HalResult;
 using android::power::PowerHalController;
 
diff --git a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
index 167f3a6..111b5d7 100644
--- a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
@@ -16,10 +16,10 @@
 
 #define LOG_TAG "PowerHalHidlBenchmarks"
 
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
 #include <benchmark/benchmark.h>
 #include <hardware/power.h>
 #include <hardware_legacy/power.h>
@@ -27,8 +27,6 @@
 #include <chrono>
 
 using android::hardware::Return;
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
 using android::hardware::power::V1_0::Feature;
 using android::hardware::power::V1_0::PowerHint;
 using std::chrono::microseconds;
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 54dffcf..6fc96c0 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -23,6 +23,10 @@
 
 cc_test {
     name: "libpowermanager_test",
+    defaults: [
+        "android.hardware.power-ndk_shared",
+        "android.hardware.power-ndk_shared",
+    ],
     test_suites: ["device-tests"],
     srcs: [
         "IThermalManagerTest.cpp",
@@ -43,6 +47,7 @@
     shared_libs: [
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libpowermanager",
@@ -51,9 +56,9 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
     ],
     static_libs: [
         "libgmock",
     ],
+    require_root: true,
 }
diff --git a/services/powermanager/tests/PowerHalControllerTest.cpp b/services/powermanager/tests/PowerHalControllerTest.cpp
index 6cc7a6f..01270ce 100644
--- a/services/powermanager/tests/PowerHalControllerTest.cpp
+++ b/services/powermanager/tests/PowerHalControllerTest.cpp
@@ -16,9 +16,9 @@
 
 #define LOG_TAG "PowerHalControllerTest"
 
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalController.h>
@@ -26,8 +26,8 @@
 
 #include <thread>
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::hardware::power::V1_0::Feature;
 using android::hardware::power::V1_0::IPower;
 using android::hardware::power::V1_0::PowerHint;
diff --git a/services/powermanager/tests/PowerHalLoaderTest.cpp b/services/powermanager/tests/PowerHalLoaderTest.cpp
index e36deed..7d97354 100644
--- a/services/powermanager/tests/PowerHalLoaderTest.cpp
+++ b/services/powermanager/tests/PowerHalLoaderTest.cpp
@@ -16,9 +16,8 @@
 
 #define LOG_TAG "PowerHalLoaderTest"
 
-#include <android-base/logging.h>
+#include <aidl/android/hardware/power/IPower.h>
 #include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/IPower.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalLoader.h>
 
@@ -28,7 +27,7 @@
 using IPowerV1_1 = android::hardware::power::V1_1::IPower;
 using IPowerV1_2 = android::hardware::power::V1_2::IPower;
 using IPowerV1_3 = android::hardware::power::V1_3::IPower;
-using IPowerAidl = android::hardware::power::IPower;
+using IPowerAidl = aidl::android::hardware::power::IPower;
 
 using namespace android;
 using namespace android::power;
@@ -37,30 +36,30 @@
 // -------------------------------------------------------------------------------------------------
 
 template <typename T>
-sp<T> loadHal();
+T loadHal();
 
 template <>
-sp<IPowerAidl> loadHal<IPowerAidl>() {
+std::shared_ptr<IPowerAidl> loadHal<std::shared_ptr<IPowerAidl>>() {
     return PowerHalLoader::loadAidl();
 }
 
 template <>
-sp<IPowerV1_0> loadHal<IPowerV1_0>() {
+sp<IPowerV1_0> loadHal<sp<IPowerV1_0>>() {
     return PowerHalLoader::loadHidlV1_0();
 }
 
 template <>
-sp<IPowerV1_1> loadHal<IPowerV1_1>() {
+sp<IPowerV1_1> loadHal<sp<IPowerV1_1>>() {
     return PowerHalLoader::loadHidlV1_1();
 }
 
 template <>
-sp<IPowerV1_2> loadHal<IPowerV1_2>() {
+sp<IPowerV1_2> loadHal<sp<IPowerV1_2>>() {
     return PowerHalLoader::loadHidlV1_2();
 }
 
 template <>
-sp<IPowerV1_3> loadHal<IPowerV1_3>() {
+sp<IPowerV1_3> loadHal<sp<IPowerV1_3>>() {
     return PowerHalLoader::loadHidlV1_3();
 }
 
@@ -69,46 +68,47 @@
 template <typename T>
 class PowerHalLoaderTest : public Test {
 public:
-    sp<T> load() { return ::loadHal<T>(); }
+    T load() { return ::loadHal<T>(); }
     void unload() { PowerHalLoader::unloadAll(); }
 };
 
 // -------------------------------------------------------------------------------------------------
-
-typedef ::testing::Types<IPowerAidl, IPowerV1_0, IPowerV1_1, IPowerV1_2, IPowerV1_3> PowerHalTypes;
+typedef ::testing::Types<std::shared_ptr<IPowerAidl>, sp<IPowerV1_0>, sp<IPowerV1_1>,
+                         sp<IPowerV1_2>, sp<IPowerV1_3>>
+        PowerHalTypes;
 TYPED_TEST_SUITE(PowerHalLoaderTest, PowerHalTypes);
 
 TYPED_TEST(PowerHalLoaderTest, TestLoadsOnlyOnce) {
-    sp<TypeParam> firstHal = this->load();
+    TypeParam firstHal = this->load();
     if (firstHal == nullptr) {
         ALOGE("Power HAL not available. Skipping test.");
         return;
     }
-    sp<TypeParam> secondHal = this->load();
+    TypeParam secondHal = this->load();
     ASSERT_EQ(firstHal, secondHal);
 }
 
 TYPED_TEST(PowerHalLoaderTest, TestUnload) {
-    sp<TypeParam> firstHal = this->load();
+    TypeParam firstHal = this->load();
     if (firstHal == nullptr) {
         ALOGE("Power HAL not available. Skipping test.");
         return;
     }
     this->unload();
-    sp<TypeParam> secondHal = this->load();
+    TypeParam secondHal = this->load();
     ASSERT_NE(secondHal, nullptr);
     ASSERT_NE(firstHal, secondHal);
 }
 
 TYPED_TEST(PowerHalLoaderTest, TestLoadMultiThreadLoadsOnlyOnce) {
-    std::vector<std::future<sp<TypeParam>>> futures;
+    std::vector<std::future<TypeParam>> futures;
     for (int i = 0; i < 10; i++) {
         futures.push_back(
                 std::async(std::launch::async, &PowerHalLoaderTest<TypeParam>::load, this));
     }
 
     futures[0].wait();
-    sp<TypeParam> firstHal = futures[0].get();
+    TypeParam firstHal = futures[0].get();
     if (firstHal == nullptr) {
         ALOGE("Power HAL not available. Skipping test.");
         return;
@@ -116,7 +116,7 @@
 
     for (int i = 1; i < 10; i++) {
         futures[i].wait();
-        sp<TypeParam> currentHal = futures[i].get();
+        TypeParam currentHal = futures[i].get();
         ASSERT_EQ(firstHal, currentHal);
     }
 }
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index cb1a77a..641ba9f 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -16,9 +16,9 @@
 
 #define LOG_TAG "PowerHalWrapperAidlTest"
 
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <binder/IServiceManager.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -28,11 +28,11 @@
 #include <unistd.h>
 #include <thread>
 
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPower;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
 using android::binder::Status;
-using android::hardware::power::Boost;
-using android::hardware::power::IPower;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
 
 using namespace android;
 using namespace android::power;
@@ -43,18 +43,21 @@
 
 class MockIPower : public IPower {
 public:
-    MOCK_METHOD(Status, isBoostSupported, (Boost boost, bool* ret), (override));
-    MOCK_METHOD(Status, setBoost, (Boost boost, int32_t durationMs), (override));
-    MOCK_METHOD(Status, isModeSupported, (Mode mode, bool* ret), (override));
-    MOCK_METHOD(Status, setMode, (Mode mode, bool enabled), (override));
-    MOCK_METHOD(Status, createHintSession,
+    MockIPower() = default;
+
+    MOCK_METHOD(ndk::ScopedAStatus, isBoostSupported, (Boost boost, bool* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setBoost, (Boost boost, int32_t durationMs), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, isModeSupported, (Mode mode, bool* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setMode, (Mode mode, bool enabled), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSession,
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                 int64_t durationNanos, sp<IPowerHintSession>* session),
+                 int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
-    MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * rate), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -65,13 +68,13 @@
 
 protected:
     std::unique_ptr<HalWrapper> mWrapper = nullptr;
-    sp<StrictMock<MockIPower>> mMockHal = nullptr;
+    std::shared_ptr<StrictMock<MockIPower>> mMockHal = nullptr;
 };
 
 // -------------------------------------------------------------------------------------------------
 
 void PowerHalWrapperAidlTest::SetUp() {
-    mMockHal = new StrictMock<MockIPower>();
+    mMockHal = ndk::SharedRefBase::make<StrictMock<MockIPower>>();
     mWrapper = std::make_unique<AidlHalWrapper>(mMockHal);
     ASSERT_NE(nullptr, mWrapper);
 }
@@ -83,9 +86,11 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::DISPLAY_UPDATE_IMMINENT), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(true),
+                                Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
         EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::DISPLAY_UPDATE_IMMINENT), Eq(100)))
-                .Times(Exactly(1));
+                .Times(Exactly(1))
+                .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
     }
 
     auto result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 100);
@@ -97,13 +102,14 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(true),
+                                Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
         EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::INTERACTION), Eq(100)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+                .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
         EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::DISPLAY_UPDATE_IMMINENT), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+                .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
     }
 
     auto result = mWrapper->setBoost(Boost::INTERACTION, 100);
@@ -115,7 +121,8 @@
 TEST_F(PowerHalWrapperAidlTest, TestSetBoostUnsupported) {
     EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<1>(false),
+                            Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
 
     auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
     ASSERT_TRUE(result.isUnsupported());
@@ -128,8 +135,13 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
-        EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::INTERACTION), Eq(100))).Times(Exactly(10));
+                .WillOnce(DoAll(SetArgPointee<1>(true),
+                                Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+        auto& exp = EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::INTERACTION), Eq(100)))
+                            .Times(Exactly(10));
+        for (int i = 0; i < 10; i++) {
+            exp.WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+        }
     }
 
     std::vector<std::thread> threads;
@@ -147,9 +159,11 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::DISPLAY_INACTIVE), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(true),
+                                Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
         EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::DISPLAY_INACTIVE), Eq(false)))
-                .Times(Exactly(1));
+                .Times(Exactly(1))
+                .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
     }
 
     auto result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, false);
@@ -161,13 +175,14 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::LAUNCH), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(true),
+                                Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
         EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::LAUNCH), Eq(true)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+                .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
         EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::DISPLAY_INACTIVE), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+                .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
     }
 
     auto result = mWrapper->setMode(Mode::LAUNCH, true);
@@ -179,14 +194,16 @@
 TEST_F(PowerHalWrapperAidlTest, TestSetModeUnsupported) {
     EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::LAUNCH), _))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<1>(false),
+                            Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
 
     auto result = mWrapper->setMode(Mode::LAUNCH, true);
     ASSERT_TRUE(result.isUnsupported());
 
     EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::CAMERA_STREAMING_HIGH), _))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<1>(false),
+                            Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
     result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true);
     ASSERT_TRUE(result.isUnsupported());
 }
@@ -196,8 +213,13 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::LAUNCH), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
-        EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::LAUNCH), Eq(false))).Times(Exactly(10));
+                .WillOnce(DoAll(SetArgPointee<1>(true),
+                                Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+        auto& exp = EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::LAUNCH), Eq(false)))
+                            .Times(Exactly(10));
+        for (int i = 0; i < 10; i++) {
+            exp.WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+        }
     }
 
     std::vector<std::thread> threads;
@@ -217,7 +239,8 @@
     int64_t durationNanos = 16666666L;
     EXPECT_CALL(*mMockHal.get(),
                 createHintSession(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), _))
-            .Times(Exactly(1));
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
     auto result = mWrapper->createHintSession(tgid, uid, threadIds, durationNanos);
     ASSERT_TRUE(result.isOk());
 }
@@ -230,13 +253,16 @@
     EXPECT_CALL(*mMockHal.get(),
                 createHintSession(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), _))
             .Times(Exactly(1))
-            .WillRepeatedly(Return(Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT)));
+            .WillOnce(Return(testing::ByMove(
+                    ndk::ScopedAStatus::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT))));
     auto result = mWrapper->createHintSession(tgid, uid, threadIds, durationNanos);
     ASSERT_TRUE(result.isFailed());
 }
 
 TEST_F(PowerHalWrapperAidlTest, TestGetHintSessionPreferredRate) {
-    EXPECT_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).Times(Exactly(1));
+    EXPECT_CALL(*mMockHal.get(), getHintSessionPreferredRate(_))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
     auto result = mWrapper->getHintSessionPreferredRate();
     ASSERT_TRUE(result.isOk());
     int64_t rate = result.value();
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
index 0cd2e22..461143b 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
@@ -16,17 +16,17 @@
 
 #define LOG_TAG "PowerHalWrapperHidlV1_0Test"
 
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <binder/IServiceManager.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::hardware::power::V1_0::Feature;
 using android::hardware::power::V1_0::IPower;
 using android::hardware::power::V1_0::PowerHint;
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
index 32f84e2..79dd996 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
@@ -16,18 +16,18 @@
 
 #define LOG_TAG "PowerHalWrapperHidlV1_1Test"
 
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
 #include <binder/IServiceManager.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::hardware::power::V1_0::Feature;
 using android::hardware::power::V1_0::PowerHint;
 using IPowerV1_1 = android::hardware::power::V1_1::IPower;
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp
index cf48409..aa6d6c7 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp
@@ -16,18 +16,18 @@
 
 #define LOG_TAG "PowerHalWrapperHidlV1_2Test"
 
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android/hardware/power/1.2/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
 #include <binder/IServiceManager.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::hardware::power::V1_0::Feature;
 using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint;
 using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint;
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp
index 2c48537..a995afd 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp
@@ -16,18 +16,18 @@
 
 #define LOG_TAG "PowerHalWrapperHidlV1_3Test"
 
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <android/hardware/power/1.3/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
 #include <binder/IServiceManager.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::hardware::power::V1_0::Feature;
 using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint;
 using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint;
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index 3155b4c..dd83fde 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -37,6 +37,8 @@
 #include <cinttypes>
 #include <cstddef>
 #include <thread>
+#include <mutex>
+#include <condition_variable>
 
 using namespace android::hardware::sensors;
 using android::util::ProtoOutputStream;
@@ -65,7 +67,7 @@
 
 } // anonymous namespace
 
-SensorDevice::SensorDevice() {
+SensorDevice::SensorDevice() : mInHalBypassMode(false) {
     if (!connectHalService()) {
         return;
     }
@@ -352,13 +354,17 @@
     if (mHalWrapper == nullptr) return NO_INIT;
 
     ssize_t eventsRead = 0;
-    if (mHalWrapper->supportsMessageQueues()) {
-        eventsRead = mHalWrapper->pollFmq(buffer, count);
-    } else if (mHalWrapper->supportsPolling()) {
-        eventsRead = mHalWrapper->poll(buffer, count);
+    if (mInHalBypassMode) [[unlikely]] {
+        eventsRead = getHalBypassInjectedEvents(buffer, count);
     } else {
-        ALOGE("Must support polling or FMQ");
-        eventsRead = -1;
+        if (mHalWrapper->supportsMessageQueues()) {
+            eventsRead = mHalWrapper->pollFmq(buffer, count);
+        } else if (mHalWrapper->supportsPolling()) {
+            eventsRead = mHalWrapper->poll(buffer, count);
+        } else {
+            ALOGE("Must support polling or FMQ");
+            eventsRead = -1;
+        }
     }
 
     if (eventsRead > 0) {
@@ -762,11 +768,38 @@
              injected_sensor_event->data[2], injected_sensor_event->data[3],
              injected_sensor_event->data[4], injected_sensor_event->data[5]);
 
+    if (mInHalBypassMode) {
+        std::lock_guard _l(mHalBypassLock);
+        mHalBypassInjectedEventQueue.push(*injected_sensor_event);
+        mHalBypassCV.notify_one();
+        return OK;
+    }
     return mHalWrapper->injectSensorData(injected_sensor_event);
 }
 
 status_t SensorDevice::setMode(uint32_t mode) {
     if (mHalWrapper == nullptr) return NO_INIT;
+    if (mode == SensorService::Mode::HAL_BYPASS_REPLAY_DATA_INJECTION) {
+        if (!mInHalBypassMode) {
+            std::lock_guard _l(mHalBypassLock);
+            while (!mHalBypassInjectedEventQueue.empty()) {
+                // flush any stale events from the injected event queue
+                mHalBypassInjectedEventQueue.pop();
+            }
+            mInHalBypassMode = true;
+        }
+        return OK;
+    } else {
+        if (mInHalBypassMode) {
+            // We are transitioning out of HAL Bypass mode. We need to notify the reader thread
+            // (specifically getHalBypassInjectedEvents()) of this change in state so that it is not
+            // stuck waiting on more injected events to come and therefore preventing events coming
+            // from the HAL from being read.
+            std::lock_guard _l(mHalBypassLock);
+            mInHalBypassMode = false;
+            mHalBypassCV.notify_one();
+        }
+    }
     return mHalWrapper->setOperationMode(static_cast<SensorService::Mode>(mode));
 }
 
@@ -872,5 +905,24 @@
     return 0;
 }
 
+ssize_t SensorDevice::getHalBypassInjectedEvents(sensors_event_t* buffer,
+                                                 size_t maxNumEventsToRead) {
+    std::unique_lock _l(mHalBypassLock);
+    if (mHalBypassInjectedEventQueue.empty()) {
+        // if the injected event queue is empty, block and wait till there are events to process
+        // or if we are no longer in HAL Bypass mode so that this method is not called in a tight
+        // loop. Otherwise, continue copying the injected events into the supplied buffer.
+        mHalBypassCV.wait(_l, [this] {
+            return (!mHalBypassInjectedEventQueue.empty() || !mInHalBypassMode);
+        });
+    }
+    size_t eventsToRead = std::min(mHalBypassInjectedEventQueue.size(), maxNumEventsToRead);
+    for (size_t i = 0; i < eventsToRead; i++) {
+        buffer[i] = mHalBypassInjectedEventQueue.front();
+        mHalBypassInjectedEventQueue.pop();
+    }
+    return eventsToRead;
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index 747a6b0..f127c0f 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -35,6 +35,9 @@
 #include <utils/Timers.h>
 
 #include <algorithm> //std::max std::min
+#include <condition_variable>
+#include <mutex>
+#include <queue>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -225,6 +228,12 @@
     float getResolutionForSensor(int sensorHandle);
 
     bool mIsDirectReportSupported;
+
+    std::mutex mHalBypassLock;
+    std::condition_variable mHalBypassCV;
+    std::queue<sensors_event_t> mHalBypassInjectedEventQueue;
+    ssize_t getHalBypassInjectedEvents(sensors_event_t* buffer, size_t count);
+    bool mInHalBypassMode;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorFusion.cpp b/services/sensorservice/SensorFusion.cpp
index 5c00260..16088ca 100644
--- a/services/sensorservice/SensorFusion.cpp
+++ b/services/sensorservice/SensorFusion.cpp
@@ -42,17 +42,20 @@
 
     if (count > 0) {
         for (size_t i=0 ; i<size_t(count) ; i++) {
-            if (list[i].type == SENSOR_TYPE_ACCELEROMETER) {
-                mAcc = Sensor(list + i);
-            }
-            if (list[i].type == SENSOR_TYPE_MAGNETIC_FIELD) {
-                mMag = Sensor(list + i);
-            }
-            if (list[i].type == SENSOR_TYPE_GYROSCOPE) {
-                mGyro = Sensor(list + i);
-            }
-            if (list[i].type == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
-                uncalibratedGyro = Sensor(list + i);
+            // Only use non-wakeup sensors
+            if ((list[i].flags & SENSOR_FLAG_WAKE_UP) == 0) {
+                if (list[i].type == SENSOR_TYPE_ACCELEROMETER) {
+                    mAcc = Sensor(list + i);
+                }
+                if (list[i].type == SENSOR_TYPE_MAGNETIC_FIELD) {
+                    mMag = Sensor(list + i);
+                }
+                if (list[i].type == SENSOR_TYPE_GYROSCOPE) {
+                    mGyro = Sensor(list + i);
+                }
+                if (list[i].type == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
+                    uncalibratedGyro = Sensor(list + i);
+                }
             }
         }
 
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index ad0ed4a..44d0d70 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -63,8 +63,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <condition_variable>
 #include <ctime>
 #include <future>
+#include <mutex>
 #include <string>
 
 #include <private/android_filesystem_config.h>
@@ -196,6 +198,16 @@
     if (mRuntimeSensorCallbacks.find(deviceId) == mRuntimeSensorCallbacks.end()) {
         mRuntimeSensorCallbacks.emplace(deviceId, callback);
     }
+
+    if (mRuntimeSensorHandler == nullptr) {
+        mRuntimeSensorEventBuffer =
+                new sensors_event_t[SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT];
+        mRuntimeSensorHandler = new RuntimeSensorHandler(this);
+        // Use PRIORITY_URGENT_DISPLAY as the injected sensor events should be dispatched as soon as
+        // possible, and also for consistency within the SensorService.
+        mRuntimeSensorHandler->run("RuntimeSensorHandler", PRIORITY_URGENT_DISPLAY);
+    }
+
     return handle;
 }
 
@@ -232,8 +244,9 @@
 }
 
 status_t SensorService::sendRuntimeSensorEvent(const sensors_event_t& event) {
-    Mutex::Autolock _l(mLock);
+    std::unique_lock<std::mutex> lock(mRutimeSensorThreadMutex);
     mRuntimeSensorEventQueue.push(event);
+    mRuntimeSensorsCv.notify_all();
     return OK;
 }
 
@@ -458,6 +471,7 @@
             const size_t minBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;
             mSensorEventBuffer = new sensors_event_t[minBufferSize];
             mSensorEventScratch = new sensors_event_t[minBufferSize];
+            mRuntimeSensorEventBuffer = nullptr;
             mMapFlushEventsToConnections = new wp<const SensorEventConnection> [minBufferSize];
             mCurrentOperatingMode = NORMAL;
 
@@ -651,6 +665,10 @@
                    result.appendFormat(" REPLAY_DATA_INJECTION : %s\n",
                             mAllowListedPackage.c_str());
                    break;
+               case HAL_BYPASS_REPLAY_DATA_INJECTION:
+                   result.appendFormat(" HAL_BYPASS_REPLAY_DATA_INJECTION : %s\n",
+                            mAllowListedPackage.c_str());
+                   break;
                default:
                    result.appendFormat(" UNKNOWN\n");
                    break;
@@ -1089,7 +1107,6 @@
         recordLastValueLocked(mSensorEventBuffer, count);
 
         // handle virtual sensors
-        bool bufferNeedsSorting = false;
         if (count && vcount) {
             sensors_event_t const * const event = mSensorEventBuffer;
             if (!mActiveVirtualSensors.empty()) {
@@ -1125,37 +1142,11 @@
                     // record the last synthesized values
                     recordLastValueLocked(&mSensorEventBuffer[count], k);
                     count += k;
-                    bufferNeedsSorting = true;
+                    sortEventBuffer(mSensorEventBuffer, count);
                 }
             }
         }
 
-        // handle runtime sensors
-        {
-            size_t k = 0;
-            while (!mRuntimeSensorEventQueue.empty()) {
-                if (count + k >= minBufferSize) {
-                    ALOGE("buffer too small to hold all events: count=%zd, k=%zu, size=%zu",
-                          count, k, minBufferSize);
-                    break;
-                }
-                mSensorEventBuffer[count + k] = mRuntimeSensorEventQueue.front();
-                mRuntimeSensorEventQueue.pop();
-                k++;
-            }
-            if (k) {
-                // record the last synthesized values
-                recordLastValueLocked(&mSensorEventBuffer[count], k);
-                count += k;
-                bufferNeedsSorting = true;
-            }
-        }
-
-        if (bufferNeedsSorting) {
-            // sort the buffer by time-stamps
-            sortEventBuffer(mSensorEventBuffer, count);
-        }
-
         // handle backward compatibility for RotationVector sensor
         if (halVersion < SENSORS_DEVICE_API_VERSION_1_0) {
             for (int i = 0; i < count; i++) {
@@ -1234,7 +1225,7 @@
         bool needsWakeLock = false;
         for (const sp<SensorEventConnection>& connection : activeConnections) {
             connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch,
-                    mMapFlushEventsToConnections);
+                                   mMapFlushEventsToConnections);
             needsWakeLock |= connection->needsWakeLock();
             // If the connection has one-shot sensors, it may be cleaned up after first trigger.
             // Early check for one-shot sensors.
@@ -1253,6 +1244,46 @@
     return false;
 }
 
+void SensorService::processRuntimeSensorEvents() {
+    size_t count = 0;
+    const size_t maxBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+
+    {
+        std::unique_lock<std::mutex> lock(mRutimeSensorThreadMutex);
+
+        if (mRuntimeSensorEventQueue.empty()) {
+            mRuntimeSensorsCv.wait(lock, [this] { return !mRuntimeSensorEventQueue.empty(); });
+        }
+
+        // Pop the events from the queue into the buffer until it's empty or the buffer is full.
+        while (!mRuntimeSensorEventQueue.empty()) {
+            if (count >= maxBufferSize) {
+                ALOGE("buffer too small to hold all events: count=%zd, size=%zu", count,
+                      maxBufferSize);
+                break;
+            }
+            mRuntimeSensorEventBuffer[count] = mRuntimeSensorEventQueue.front();
+            mRuntimeSensorEventQueue.pop();
+            count++;
+        }
+    }
+
+    if (count) {
+        ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+
+        recordLastValueLocked(mRuntimeSensorEventBuffer, count);
+        sortEventBuffer(mRuntimeSensorEventBuffer, count);
+
+        for (const sp<SensorEventConnection>& connection : connLock.getActiveConnections()) {
+            connection->sendEvents(mRuntimeSensorEventBuffer, count, /* scratch= */ nullptr,
+                                   /* mapFlushEventsToConnections= */ nullptr);
+            if (connection->hasOneShotSensors()) {
+                cleanupAutoDisabledSensorLocked(connection, mRuntimeSensorEventBuffer, count);
+            }
+        }
+    }
+}
+
 sp<Looper> SensorService::getLooper() const {
     return mLooper;
 }
@@ -1300,6 +1331,14 @@
     return false;
 }
 
+bool SensorService::RuntimeSensorHandler::threadLoop() {
+    ALOGD("new thread RuntimeSensorHandler");
+    do {
+        mService->processRuntimeSensorEvents();
+    } while (!Thread::exitPending());
+    return false;
+}
+
 void SensorService::recordLastValueLocked(
         const sensors_event_t* buffer, size_t count) {
     for (size_t i = 0; i < count; i++) {
@@ -1494,10 +1533,9 @@
 
 sp<ISensorEventConnection> SensorService::createSensorEventConnection(const String8& packageName,
         int requestedMode, const String16& opPackageName, const String16& attributionTag) {
-    // Only 3 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION and
-    // REPLAY_DATA_INJECTION.
-    if (requestedMode != NORMAL && requestedMode != DATA_INJECTION &&
-            requestedMode != REPLAY_DATA_INJECTION) {
+    // Only 4 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION,
+    // REPLAY_DATA_INJECTION and HAL_BYPASS_REPLAY_DATA_INJECTION
+    if (requestedMode != NORMAL && !isInjectionMode(requestedMode)) {
         return nullptr;
     }
     resetTargetSdkVersionCache(opPackageName);
@@ -1518,9 +1556,9 @@
     String16 connOpPackageName =
             (opPackageName == String16("")) ? String16(connPackageName) : opPackageName;
     sp<SensorEventConnection> result(new SensorEventConnection(this, uid, connPackageName,
-            requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION,
-            connOpPackageName, attributionTag));
-    if (requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION) {
+                                                               isInjectionMode(requestedMode),
+                                                               connOpPackageName, attributionTag));
+    if (isInjectionMode(requestedMode)) {
         mConnectionHolder.addEventConnectionIfNotPresent(result);
         // Add the associated file descriptor to the Looper for polling whenever there is data to
         // be injected.
@@ -1531,7 +1569,22 @@
 
 int SensorService::isDataInjectionEnabled() {
     Mutex::Autolock _l(mLock);
-    return (mCurrentOperatingMode == DATA_INJECTION);
+    return mCurrentOperatingMode == DATA_INJECTION;
+}
+
+int SensorService::isReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    return mCurrentOperatingMode == REPLAY_DATA_INJECTION;
+}
+
+int SensorService::isHalBypassReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    return mCurrentOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION;
+}
+
+bool SensorService::isInjectionMode(int mode) {
+    return (mode == DATA_INJECTION || mode == REPLAY_DATA_INJECTION ||
+            mode == HAL_BYPASS_REPLAY_DATA_INJECTION);
 }
 
 sp<ISensorEventConnection> SensorService::createSensorDirectConnection(
@@ -2297,6 +2350,10 @@
       *targetModeOut = REPLAY_DATA_INJECTION;
       return true;
     }
+    if (inputString == std::string("hal_bypass_replay_data_injection")) {
+      *targetModeOut = HAL_BYPASS_REPLAY_DATA_INJECTION;
+      return true;
+    }
     return false;
 }
 
@@ -2322,7 +2379,8 @@
             dev.disableAllSensors();
         }
         if (mCurrentOperatingMode == DATA_INJECTION ||
-                mCurrentOperatingMode == REPLAY_DATA_INJECTION) {
+                mCurrentOperatingMode == REPLAY_DATA_INJECTION ||
+                mCurrentOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION) {
           resetToNormalModeLocked();
         }
         mAllowListedPackage.clear();
@@ -2338,6 +2396,8 @@
         disableAllSensorsLocked(&connLock);
         mAllowListedPackage = String8(args[1]);
         return status_t(NO_ERROR);
+      case HAL_BYPASS_REPLAY_DATA_INJECTION:
+        FALLTHROUGH_INTENDED;
       case REPLAY_DATA_INJECTION:
         if (SensorServiceUtil::isUserBuild()) {
             return INVALID_OPERATION;
@@ -2346,9 +2406,16 @@
       case DATA_INJECTION:
         if (mCurrentOperatingMode == NORMAL) {
             dev.disableAllSensors();
-            // Always use DATA_INJECTION here since this value goes to the HAL and the HAL
-            // doesn't have an understanding of replay vs. normal data injection.
-            status_t err = dev.setMode(DATA_INJECTION);
+            status_t err = NO_ERROR;
+            if (targetOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION) {
+                // Set SensorDevice to HAL_BYPASS_REPLAY_DATA_INJECTION_MODE. This value is not
+                // injected into the HAL, nor will any events be injected into the HAL
+                err = dev.setMode(HAL_BYPASS_REPLAY_DATA_INJECTION);
+            } else {
+                // Otherwise use DATA_INJECTION here since this value goes to the HAL and the HAL
+                // doesn't have an understanding of replay vs. normal data injection.
+                err = dev.setMode(DATA_INJECTION);
+            }
             if (err == NO_ERROR) {
                 mCurrentOperatingMode = targetOperatingMode;
             }
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 545f6c2..b643f6b 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -42,6 +42,8 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <condition_variable>
+#include <mutex>
 #include <queue>
 #include <unordered_map>
 #include <unordered_set>
@@ -120,6 +122,11 @@
        // delivered to all requesting apps rather than just the package allowed to inject data.
        // This mode is only allowed to be used on development builds.
        REPLAY_DATA_INJECTION = 3,
+       // Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+       // buffer in SensorDevice and played back to SensorService when SensorDevice::poll() is
+       // called. This is useful for playing back sensor data on the platform without relying on
+       // the HAL to support data injection.
+       HAL_BYPASS_REPLAY_DATA_INJECTION = 4,
 
       // State Transitions supported.
       //     RESTRICTED   <---  NORMAL   ---> DATA_INJECTION/REPLAY_DATA_INJECTION
@@ -208,6 +215,7 @@
     class SensorEventAckReceiver;
     class SensorRecord;
     class SensorRegistrationInfo;
+    class RuntimeSensorHandler;
 
     // Promoting a SensorEventConnection or SensorDirectConnection from wp to sp must be done with
     // mLock held, but destroying that sp must be done unlocked to avoid a race condition that
@@ -264,6 +272,14 @@
         SortedVector< wp<SensorDirectConnection> > mDirectConnections;
     };
 
+    class RuntimeSensorHandler : public Thread {
+        sp<SensorService> const mService;
+    public:
+        virtual bool threadLoop();
+        explicit RuntimeSensorHandler(const sp<SensorService>& service) : mService(service) {
+        }
+    };
+
     // If accessing a sensor we need to make sure the UID has access to it. If
     // the app UID is idle then it cannot access sensors and gets no trigger
     // events, no on-change events, flush event behavior does not change, and
@@ -368,6 +384,8 @@
     // Thread interface
     virtual bool threadLoop();
 
+    void processRuntimeSensorEvents();
+
     // ISensorServer interface
     virtual Vector<Sensor> getSensorList(const String16& opPackageName);
     virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName);
@@ -376,6 +394,8 @@
             const String8& packageName,
             int requestedMode, const String16& opPackageName, const String16& attributionTag);
     virtual int isDataInjectionEnabled();
+    virtual int isReplayDataInjectionEnabled();
+    virtual int isHalBypassReplayDataInjectionEnabled();
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             int deviceId, uint32_t size, int32_t type, int32_t format,
             const native_handle *resource);
@@ -494,6 +514,8 @@
     // Removes the capped rate on active direct connections (when the mic toggle is flipped to off)
     void uncapRates();
 
+    bool isInjectionMode(int mode);
+
     static inline bool isAudioServerOrSystemServerUid(uid_t uid) {
         return multiuser_get_app_id(uid) == AID_SYSTEM || uid == AID_AUDIOSERVER;
     }
@@ -512,6 +534,10 @@
     uint32_t mSocketBufferSize;
     sp<Looper> mLooper;
     sp<SensorEventAckReceiver> mAckReceiver;
+    sp<RuntimeSensorHandler> mRuntimeSensorHandler;
+    // Mutex and CV used to notify the mRuntimeSensorHandler thread that there are new events.
+    std::mutex mRutimeSensorThreadMutex;
+    std::condition_variable mRuntimeSensorsCv;
 
     // protected by mLock
     mutable Mutex mLock;
@@ -519,7 +545,7 @@
     std::unordered_set<int> mActiveVirtualSensors;
     SensorConnectionHolder mConnectionHolder;
     bool mWakeLockAcquired;
-    sensors_event_t *mSensorEventBuffer, *mSensorEventScratch;
+    sensors_event_t *mSensorEventBuffer, *mSensorEventScratch, *mRuntimeSensorEventBuffer;
     // WARNING: these SensorEventConnection instances must not be promoted to sp, except via
     // modification to add support for them in ConnectionSafeAutolock
     wp<const SensorEventConnection> * mMapFlushEventsToConnections;
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index ed4829a..f6f104e 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -17,6 +17,7 @@
     static_libs: [
         "libsensorserviceaidl",
         "libpermission",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
         "android.frameworks.sensorservice-V1-ndk",
         "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V2-ndk",
@@ -46,7 +47,6 @@
                 "unsigned-integer-overflow",
             ],
         },
-        address: true,
         integer_overflow: true,
     },
 
diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp
index 88521f1..92956d6 100644
--- a/services/sensorservice/tests/sensorservicetest.cpp
+++ b/services/sensorservice/tests/sensorservicetest.cpp
@@ -87,7 +87,7 @@
     int ret = mgr.createDirectChannel(
             kMemSize, ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY, resourceHandle);
 
-    // Should print -22 (BAD_VALUE) and the device runtime shouldn't restart
+    // Should not succeed (ret != OK) and the device runtime shouldn't restart
     printf("createInvalidDirectChannel=%d\n", ret);
 
     // Secondary test: correct channel creation & destruction (should print 0)
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 0e6d8b1..9d0f285 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -7,6 +7,17 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "surfaceflinger_flags",
+    package: "com.android.graphics.surfaceflinger.flags",
+    srcs: ["surfaceflinger_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libsurfaceflingerflags",
+    aconfig_declarations: "surfaceflinger_flags",
+}
+
 cc_defaults {
     name: "surfaceflinger_defaults",
     cflags: [
@@ -26,7 +37,9 @@
     name: "libsurfaceflinger_defaults",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
+        "android.hardware.power-ndk_shared",
         "librenderengine_deps",
+        "libtimestats_deps",
         "surfaceflinger_defaults",
     ],
     cflags: [
@@ -47,7 +60,6 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
-        "android.hardware.power-V4-cpp",
         "libbase",
         "libbinder",
         "libbinder_ndk",
@@ -58,14 +70,12 @@
         "libGLESv2",
         "libgui",
         "libhidlbase",
-        "liblayers_proto",
         "liblog",
         "libnativewindow",
         "libpowermanager",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libsync",
-        "libtimestats",
         "libui",
         "libinput",
         "libutils",
@@ -77,12 +87,15 @@
         "libcompositionengine",
         "libframetimeline",
         "libgui_aidl_static",
+        "liblayers_proto",
         "libperfetto_client_experimental",
         "librenderengine",
         "libscheduler",
         "libserviceutils",
         "libshaders",
+        "libtimestats",
         "libtonemap",
+        "libsurfaceflingerflags",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
@@ -95,6 +108,7 @@
         "libcompositionengine",
         "librenderengine",
         "libserviceutils",
+        "libtimestats",
     ],
     export_shared_lib_headers: [
         "android.hardware.graphics.allocator@2.0",
@@ -106,7 +120,6 @@
         "android.hardware.graphics.composer@2.4",
         "libpowermanager",
         "libhidlbase",
-        "libtimestats",
     ],
     // TODO (marissaw): this library is not used by surfaceflinger. This is here so
     // the library compiled in a way that is accessible to system partition when running
@@ -167,6 +180,7 @@
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
+        "HdrSdrRatioOverlay.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
@@ -196,9 +210,12 @@
         "StartPropertySetThread.cpp",
         "SurfaceFlinger.cpp",
         "SurfaceFlingerDefaultFactory.cpp",
+        "Tracing/LayerDataSource.cpp",
         "Tracing/LayerTracing.cpp",
+        "Tracing/TransactionDataSource.cpp",
         "Tracing/TransactionTracing.cpp",
         "Tracing/TransactionProtoParser.cpp",
+        "Tracing/tools/LayerTraceGenerator.cpp",
         "TransactionCallbackInvoker.cpp",
         "TunnelModeEnabledReporter.cpp",
     ],
@@ -214,14 +231,12 @@
         "-DLOG_TAG=\"SurfaceFlinger\"",
     ],
     shared_libs: [
-        "android.frameworks.displayservice@1.0",
         "android.hardware.configstore-utils",
         "android.hardware.configstore@1.0",
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "libbinder",
         "libcutils",
-        "libdisplayservicehidl",
         "libhidlbase",
         "liblog",
         "libprocessgroup",
@@ -229,6 +244,8 @@
         "libutils",
     ],
     static_libs: [
+        "android.frameworks.displayservice@1.0",
+        "libdisplayservicehidl",
         "libserviceutils",
     ],
 }
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index bdbc79b..6b4215e 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -22,6 +22,7 @@
 #include <private/android_filesystem_config.h>
 
 #include <gui/AidlStatusUtil.h>
+#include <gui/SchedulingPolicy.h>
 
 #include "Client.h"
 #include "FrontEnd/LayerCreationArgs.h"
@@ -117,5 +118,9 @@
     return binderStatusFromStatusT(status);
 }
 
+binder::Status Client::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    return gui::getSchedulingPolicy(outPolicy);
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h
index af410ea..77916e0 100644
--- a/services/surfaceflinger/Client.h
+++ b/services/surfaceflinger/Client.h
@@ -55,6 +55,8 @@
 
     binder::Status mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) override;
 
+    binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
+
     // constant
     sp<SurfaceFlinger> mFlinger;
 
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 837b49a..370e4b6 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -11,7 +11,9 @@
     name: "libcompositionengine_defaults",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
+        "android.hardware.power-ndk_shared",
         "librenderengine_deps",
+        "libtimestats_deps",
         "surfaceflinger_defaults",
     ],
     cflags: [
@@ -26,22 +28,21 @@
         "android.hardware.graphics.composer@2.4",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
         "libbase",
         "libcutils",
         "libgui",
-        "liblayers_proto",
         "liblog",
         "libnativewindow",
         "libprotobuf-cpp-lite",
         "libSurfaceFlingerProp",
-        "libtimestats",
         "libui",
         "libutils",
     ],
     static_libs: [
+        "liblayers_proto",
         "libmath",
         "librenderengine",
+        "libtimestats",
         "libtonemap",
         "libaidlcommonsupport",
         "libprocessgroup",
@@ -62,6 +63,7 @@
 cc_library {
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
+    static_libs: ["libsurfaceflingerflags"],
     srcs: [
         "src/planner/CachedSet.cpp",
         "src/planner/Flattener.cpp",
@@ -106,6 +108,7 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
+        "libsurfaceflingerflags_test",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -140,6 +143,7 @@
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
+        "libsurfaceflingerflags_test",
     ],
     // For some reason, libvulkan isn't picked up from librenderengine
     // Probably ASAN related?
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 09bc467..1a8644e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -67,9 +67,6 @@
     // Controls how the color mode is chosen for an output
     OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced};
 
-    // If not Dataspace::UNKNOWN, overrides the dataspace on each output
-    ui::Dataspace colorSpaceAgnosticDataspace{ui::Dataspace::UNKNOWN};
-
     // Forces a color mode on the outputs being refreshed
     ui::ColorMode forceOutputColorMode{ui::ColorMode::NATIVE};
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
index df44e75..9c80cac 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
@@ -87,10 +87,6 @@
     // Returns true if HWC for this profile supports the dataspace
     virtual bool isDataspaceSupported(ui::Dataspace) const = 0;
 
-    // Returns the target dataspace for picked color mode and dataspace
-    virtual ui::Dataspace getTargetDataspace(ui::ColorMode, ui::Dataspace,
-                                             ui::Dataspace colorSpaceAgnosticDataspace) const = 0;
-
     // Debugging
     virtual void dump(std::string&) const = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 35ca3a5..11759b8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -26,6 +26,7 @@
 #include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
+#include <ui/ShadowSettings.h>
 #include <ui/Transform.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -132,8 +133,7 @@
     // The bounds of the layer in layer local coordinates
     FloatRect geomLayerBounds;
 
-    // length of the shadow in screen space
-    float shadowRadius{0.f};
+    ShadowSettings shadowSettings;
 
     // List of regions that require blur
     std::vector<BlurRegion> blurRegions;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index a3d8639..370c7cf 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -136,7 +136,6 @@
         ui::ColorMode mode{ui::ColorMode::NATIVE};
         ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
         ui::RenderIntent renderIntent{ui::RenderIntent::COLORIMETRIC};
-        ui::Dataspace colorSpaceAgnosticDataspace{ui::Dataspace::UNKNOWN};
     };
 
     // Use internally to incrementally compute visibility/coverage
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h
index 9bc0e68..3aad04c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h
@@ -55,7 +55,6 @@
 
     const HdrCapabilities& getHdrCapabilities() const override;
     bool isDataspaceSupported(ui::Dataspace) const override;
-    ui::Dataspace getTargetDataspace(ui::ColorMode, ui::Dataspace, ui::Dataspace) const override;
 
     void dump(std::string&) const override;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 28c6e92..6cb1e7e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -116,9 +116,6 @@
     // Current active dataspace
     ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
 
-    // Current target dataspace
-    ui::Dataspace targetDataspace{ui::Dataspace::UNKNOWN};
-
     std::optional<android::HWComposer::DeviceRequestedChanges> previousDeviceRequestedChanges{};
 
     bool previousDeviceRequestedSuccess = false;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h
index 1aaebea..a904521 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h
@@ -43,8 +43,6 @@
 
     MOCK_CONST_METHOD0(getHdrCapabilities, const HdrCapabilities&());
     MOCK_CONST_METHOD1(isDataspaceSupported, bool(ui::Dataspace));
-    MOCK_CONST_METHOD3(getTargetDataspace,
-                       ui::Dataspace(ui::ColorMode, ui::Dataspace, ui::Dataspace));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
diff --git a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
index 7e020ee..bdaa1d0 100644
--- a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -41,12 +41,10 @@
 }
 
 inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
-    return lhs.textureName == rhs.textureName &&
-            lhs.useTextureFiltering == rhs.useTextureFiltering &&
+    return lhs.useTextureFiltering == rhs.useTextureFiltering &&
             lhs.textureTransform == rhs.textureTransform &&
             lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
-            lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
-            lhs.maxLuminanceNits == rhs.maxLuminanceNits;
+            lhs.isOpaque == rhs.isOpaque && lhs.maxLuminanceNits == rhs.maxLuminanceNits;
 }
 
 inline bool equalIgnoringBuffer(const renderengine::LayerSettings& lhs,
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 85fc095..f2acfc9 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -107,14 +107,9 @@
 }
 
 void Display::setColorProfile(const ColorProfile& colorProfile) {
-    const ui::Dataspace targetDataspace =
-            getDisplayColorProfile()->getTargetDataspace(colorProfile.mode, colorProfile.dataspace,
-                                                         colorProfile.colorSpaceAgnosticDataspace);
-
     if (colorProfile.mode == getState().colorMode &&
         colorProfile.dataspace == getState().dataspace &&
-        colorProfile.renderIntent == getState().renderIntent &&
-        targetDataspace == getState().targetDataspace) {
+        colorProfile.renderIntent == getState().renderIntent) {
         return;
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp b/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp
index a7c4512..f339d41 100644
--- a/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp
@@ -220,17 +220,6 @@
     minLuminance = minLuminance <= 0.0 ? sDefaultMinLumiance : minLuminance;
     maxLuminance = maxLuminance <= 0.0 ? sDefaultMaxLumiance : maxLuminance;
     maxAverageLuminance = maxAverageLuminance <= 0.0 ? sDefaultMaxLumiance : maxAverageLuminance;
-    if (args.hasWideColorGamut) {
-        // insert HDR10/HLG as we will force client composition for HDR10/HLG
-        // layers
-        if (!hasHDR10Support()) {
-            types.push_back(ui::Hdr::HDR10);
-        }
-
-        if (!hasHLGSupport()) {
-            types.push_back(ui::Hdr::HLG);
-        }
-    }
 
     mHdrCapabilities = HdrCapabilities(types, maxLuminance, maxAverageLuminance, minLuminance);
 }
@@ -391,17 +380,6 @@
     }
 }
 
-ui::Dataspace DisplayColorProfile::getTargetDataspace(ColorMode mode, Dataspace dataspace,
-                                                      Dataspace colorSpaceAgnosticDataspace) const {
-    if (isHdrColorMode(mode)) {
-        return Dataspace::UNKNOWN;
-    }
-    if (colorSpaceAgnosticDataspace != ui::Dataspace::UNKNOWN) {
-        return colorSpaceAgnosticDataspace;
-    }
-    return dataspace;
-}
-
 void DisplayColorProfile::dump(std::string& out) const {
     out.append("   Composition Display Color State:");
 
@@ -414,6 +392,10 @@
     dumpVal(out, "dv", hasDolbyVisionSupport());
     dumpVal(out, "metadata", getSupportedPerFrameMetadata());
 
+    out.append("\n   Hdr Luminance Info:");
+    dumpVal(out, "desiredMinLuminance", mHdrCapabilities.getDesiredMinLuminance());
+    dumpVal(out, "desiredMaxLuminance", mHdrCapabilities.getDesiredMaxLuminance());
+    dumpVal(out, "desiredMaxAverageLuminance", mHdrCapabilities.getDesiredMaxAverageLuminance());
     out.append("\n");
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 426cc57..2d10866 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -68,7 +68,7 @@
     dumpVal(out, "geomLayerBounds", geomLayerBounds);
 
     out.append("      ");
-    dumpVal(out, "shadowRadius", shadowRadius);
+    dumpVal(out, "shadowLength", shadowSettings.length);
 
     out.append("\n      ");
     dumpVal(out, "blend", toString(blendMode), blendMode);
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 1205a2c..fa3733b 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -261,22 +261,16 @@
 }
 
 void Output::setColorProfile(const ColorProfile& colorProfile) {
-    ui::Dataspace targetDataspace =
-            getDisplayColorProfile()->getTargetDataspace(colorProfile.mode, colorProfile.dataspace,
-                                                         colorProfile.colorSpaceAgnosticDataspace);
-
     auto& outputState = editState();
     if (outputState.colorMode == colorProfile.mode &&
         outputState.dataspace == colorProfile.dataspace &&
-        outputState.renderIntent == colorProfile.renderIntent &&
-        outputState.targetDataspace == targetDataspace) {
+        outputState.renderIntent == colorProfile.renderIntent) {
         return;
     }
 
     outputState.colorMode = colorProfile.mode;
     outputState.dataspace = colorProfile.dataspace;
     outputState.renderIntent = colorProfile.renderIntent;
-    outputState.targetDataspace = targetDataspace;
 
     mRenderSurface->setBufferDataspace(colorProfile.dataspace);
 
@@ -469,15 +463,14 @@
 
 void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
                                 LayerFESet& layerFESet) {
-    ATRACE_CALL();
-    ALOGV(__FUNCTION__);
-
     auto& outputState = editState();
 
     // Do nothing if this output is not enabled or there is no need to perform this update
     if (!outputState.isEnabled || CC_LIKELY(!refreshArgs.updatingOutputGeometryThisFrame)) {
         return;
     }
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
 
     // Process the layers to determine visibility and coverage
     compositionengine::Output::CoverageState coverage{layerFESet};
@@ -595,10 +588,10 @@
     const Rect visibleRect(tr.transform(layerFEState->geomLayerBounds));
     visibleRegion.set(visibleRect);
 
-    if (layerFEState->shadowRadius > 0.0f) {
+    if (layerFEState->shadowSettings.length > 0.0f) {
         // if the layer casts a shadow, offset the layers visible region and
         // calculate the shadow region.
-        const auto inset = static_cast<int32_t>(ceilf(layerFEState->shadowRadius) * -1.0f);
+        const auto inset = static_cast<int32_t>(ceilf(layerFEState->shadowSettings.length) * -1.0f);
         Rect visibleRectWithShadows(visibleRect);
         visibleRectWithShadows.inset(inset, inset, inset, inset);
         visibleRegion.set(visibleRectWithShadows);
@@ -902,13 +895,19 @@
 compositionengine::OutputLayer* Output::findLayerRequestingBackgroundComposition() const {
     compositionengine::OutputLayer* layerRequestingBgComposition = nullptr;
     for (auto* layer : getOutputLayersOrderedByZ()) {
-        auto* compState = layer->getLayerFE().getCompositionState();
+        const auto* compState = layer->getLayerFE().getCompositionState();
 
         // If any layer has a sideband stream, we will disable blurs. In that case, we don't
         // want to force client composition because of the blur.
         if (compState->sidebandStream != nullptr) {
             return nullptr;
         }
+
+        // If RenderEngine cannot render protected content, we cannot blur.
+        if (compState->hasProtectedContent &&
+            !getCompositionEngine().getRenderEngine().supportsProtectedContent()) {
+            return nullptr;
+        }
         if (compState->isOpaque) {
             continue;
         }
@@ -968,7 +967,7 @@
             case ui::Dataspace::BT2020_ITU_HLG:
                 bestDataSpace = ui::Dataspace::DISPLAY_P3;
                 // When there's mixed PQ content and HLG content, we set the HDR
-                // data space to be BT2020_PQ and convert HLG to PQ.
+                // data space to be BT2020_HLG and convert PQ to HLG.
                 if (*outHdrDataSpace == ui::Dataspace::UNKNOWN) {
                     *outHdrDataSpace = ui::Dataspace::BT2020_HLG;
                 }
@@ -985,8 +984,7 @@
         const compositionengine::CompositionRefreshArgs& refreshArgs) const {
     if (refreshArgs.outputColorSetting == OutputColorSetting::kUnmanaged) {
         return ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
-                            ui::RenderIntent::COLORIMETRIC,
-                            refreshArgs.colorSpaceAgnosticDataspace};
+                            ui::RenderIntent::COLORIMETRIC};
     }
 
     ui::Dataspace hdrDataSpace;
@@ -1032,8 +1030,7 @@
     mDisplayColorProfile->getBestColorMode(bestDataSpace, intent, &outDataSpace, &outMode,
                                            &outRenderIntent);
 
-    return ColorProfile{outMode, outDataSpace, outRenderIntent,
-                        refreshArgs.colorSpaceAgnosticDataspace};
+    return ColorProfile{outMode, outDataSpace, outRenderIntent};
 }
 
 void Output::beginFrame() {
@@ -1318,17 +1315,9 @@
                    });
 
     const nsecs_t renderEngineStart = systemTime();
-    // Only use the framebuffer cache when rendering to an internal display
-    // TODO(b/173560331): This is only to help mitigate memory leaks from virtual displays because
-    // right now we don't have a concrete eviction policy for output buffers: GLESRenderEngine
-    // bounds its framebuffer cache but Skia RenderEngine has no current policy. The best fix is
-    // probably to encapsulate the output buffer into a structure that dispatches resource cleanup
-    // over to RenderEngine, in which case this flag can be removed from the drawLayers interface.
-    const bool useFramebufferCache = outputState.layerFilter.toInternalDisplay;
-
     auto fenceResult = renderEngine
                                .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, tex,
-                                           useFramebufferCache, std::move(fd))
+                                           std::move(fd))
                                .get();
 
     if (mClientCompositionRequestCache && fenceStatus(fenceResult) != NO_ERROR) {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 9713e79..39cf671 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -52,7 +52,6 @@
     dumpVal(out, "colorMode", toString(colorMode), colorMode);
     dumpVal(out, "renderIntent", toString(renderIntent), renderIntent);
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
-    dumpVal(out, "targetDataspace", toString(targetDataspace), targetDataspace);
 
     out.append("\n   ");
     dumpVal(out, "colorTransformMatrix", colorTransformMatrix);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index fc5f8ca..7fe3369 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -222,8 +222,8 @@
 
     // Some HWCs may clip client composited input to its displayFrame. Make sure
     // that this does not cut off the shadow.
-    if (layerState.forceClientComposition && layerState.shadowRadius > 0.0f) {
-        const auto outset = layerState.shadowRadius;
+    if (layerState.forceClientComposition && layerState.shadowSettings.length > 0.0f) {
+        const auto outset = layerState.shadowSettings.length;
         geomLayerBounds.left -= outset;
         geomLayerBounds.top -= outset;
         geomLayerBounds.right += outset;
@@ -311,12 +311,20 @@
         }
     }
 
+    auto pixelFormat = layerFEState->buffer ? std::make_optional(static_cast<ui::PixelFormat>(
+                                                      layerFEState->buffer->getPixelFormat()))
+                                            : std::nullopt;
+
+    auto hdrRenderType =
+            getHdrRenderType(outputState.dataspace, pixelFormat, layerFEState->desiredHdrSdrRatio);
+
     // Determine the output dependent dataspace for this layer. If it is
     // colorspace agnostic, it just uses the dataspace chosen for the output to
     // avoid the need for color conversion.
-    state.dataspace = layerFEState->isColorspaceAgnostic &&
-                    outputState.targetDataspace != ui::Dataspace::UNKNOWN
-            ? outputState.targetDataspace
+    // For now, also respect the colorspace agnostic flag if we're drawing to HDR, to avoid drastic
+    // luminance shift. TODO(b/292162273): we should check if that's true though.
+    state.dataspace = layerFEState->isColorspaceAgnostic && hdrRenderType == HdrRenderType::SDR
+            ? outputState.dataspace
             : layerFEState->dataspace;
 
     // Override the dataspace transfer from 170M to sRGB if the device configuration requests this.
@@ -330,12 +338,8 @@
                 (state.dataspace & HAL_DATASPACE_RANGE_MASK) | HAL_DATASPACE_TRANSFER_SRGB);
     }
 
-    auto pixelFormat = layerFEState->buffer ? std::make_optional(static_cast<ui::PixelFormat>(
-                                                      layerFEState->buffer->getPixelFormat()))
-                                            : std::nullopt;
-
-    // get HdrRenderType after the dataspace gets changed.
-    auto hdrRenderType =
+    // re-get HdrRenderType after the dataspace gets changed.
+    hdrRenderType =
             getHdrRenderType(state.dataspace, pixelFormat, layerFEState->desiredHdrSdrRatio);
 
     // For hdr content, treat the white point as the display brightness - HDR content should not be
@@ -840,10 +844,16 @@
 
 bool OutputLayer::needsFiltering() const {
     const auto& state = getState();
-    const auto& displayFrame = state.displayFrame;
     const auto& sourceCrop = state.sourceCrop;
-    return sourceCrop.getHeight() != displayFrame.getHeight() ||
-            sourceCrop.getWidth() != displayFrame.getWidth();
+    auto displayFrameWidth = static_cast<float>(state.displayFrame.getWidth());
+    auto displayFrameHeight = static_cast<float>(state.displayFrame.getHeight());
+
+    if (state.bufferTransform & HAL_TRANSFORM_ROT_90) {
+        std::swap(displayFrameWidth, displayFrameHeight);
+    }
+
+    return sourceCrop.getHeight() != displayFrameHeight ||
+            sourceCrop.getWidth() != displayFrameWidth;
 }
 
 std::optional<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionSettings() const {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 7547be9..579c6ba 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -275,11 +275,9 @@
         bufferFence.reset(texture->getReadyFence()->dup());
     }
 
-    constexpr bool kUseFramebufferCache = false;
-
     auto fenceResult = renderEngine
                                .drawLayers(displaySettings, layerSettings, texture->get(),
-                                           kUseFramebufferCache, std::move(bufferFence))
+                                           std::move(bufferFence))
                                .get();
 
     if (fenceStatus(fenceResult) == NO_ERROR) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
index 21b9aa9..03a97dc 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
@@ -282,39 +282,6 @@
     }
 }
 
-TEST_F(DisplayColorProfileTest, ctorSignalsHdrSupportForAnyWideColorGamutDevice) {
-    {
-        // If the output does not profile wide color gamut, then no HDR modes
-        // will be profileed in the generated HDR capabilities.
-        auto profile = ProfileFactory().setHasWideColorGamut(false).build();
-
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), IsEmpty());
-    }
-
-    {
-        // If the HWC does not show profile for certain HDR modes, then the
-        // generated HDR capabilities will indicate profile anyway.
-        auto profile = ProfileFactory().setHasWideColorGamut(true).build();
-
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), SizeIs(2));
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HDR10));
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HLG));
-    }
-
-    {
-        // If the HWC profiles the HDR modes, then the generated capabilities
-        // still has one entry for each HDR type.
-        auto profile = ProfileFactory()
-                               .setHasWideColorGamut(true)
-                               .addHdrTypes({Hdr::HLG, Hdr::HDR10})
-                               .build();
-
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), SizeIs(2));
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HDR10));
-        EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HLG));
-    }
-}
-
 /* ------------------------------------------------------------------------
  * DisplayColorProfile::hasRenderIntent
  */
@@ -646,29 +613,5 @@
     EXPECT_TRUE(profile.isDataspaceSupported(Dataspace::BT2020_ITU_HLG));
 }
 
-/*
- * RenderSurface::getTargetDataspace()
- */
-
-TEST_F(DisplayColorProfileTest, getTargetDataspaceWorks) {
-    auto profile = ProfileFactory::createProfileWithNoColorModeSupport();
-
-    // For a non-HDR colorspace with no colorSpaceAgnosticDataspace override,
-    // the input dataspace should be returned.
-    EXPECT_EQ(Dataspace::DISPLAY_P3,
-              profile.getTargetDataspace(ColorMode::DISPLAY_P3, Dataspace::DISPLAY_P3,
-                                         Dataspace::UNKNOWN));
-
-    // If colorSpaceAgnosticDataspace is set, its value should be returned
-    EXPECT_EQ(Dataspace::V0_SRGB,
-              profile.getTargetDataspace(ColorMode::DISPLAY_P3, Dataspace::DISPLAY_P3,
-                                         Dataspace::V0_SRGB));
-
-    // For an HDR colorspace, Dataspace::UNKNOWN should be returned.
-    EXPECT_EQ(Dataspace::UNKNOWN,
-              profile.getTargetDataspace(ColorMode::BT2100_PQ, Dataspace::BT2020_PQ,
-                                         Dataspace::UNKNOWN));
-}
-
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 9be6bc2..027004a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -403,23 +403,18 @@
     mock::DisplayColorProfile* colorProfile = new StrictMock<mock::DisplayColorProfile>();
     mDisplay->setDisplayColorProfileForTest(std::unique_ptr<DisplayColorProfile>(colorProfile));
 
-    EXPECT_CALL(*colorProfile, getTargetDataspace(_, _, _))
-            .WillRepeatedly(Return(ui::Dataspace::UNKNOWN));
-
     // These values are expected to be the initial state.
     ASSERT_EQ(ui::ColorMode::NATIVE, mDisplay->getState().colorMode);
     ASSERT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().dataspace);
     ASSERT_EQ(ui::RenderIntent::COLORIMETRIC, mDisplay->getState().renderIntent);
-    ASSERT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().targetDataspace);
 
     // Otherwise if the values are unchanged, nothing happens
     mDisplay->setColorProfile(ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
-                                           ui::RenderIntent::COLORIMETRIC, ui::Dataspace::UNKNOWN});
+                                           ui::RenderIntent::COLORIMETRIC});
 
     EXPECT_EQ(ui::ColorMode::NATIVE, mDisplay->getState().colorMode);
     EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().dataspace);
     EXPECT_EQ(ui::RenderIntent::COLORIMETRIC, mDisplay->getState().renderIntent);
-    EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().targetDataspace);
 
     // Otherwise if the values are different, updates happen
     EXPECT_CALL(*renderSurface, setBufferDataspace(ui::Dataspace::DISPLAY_P3)).Times(1);
@@ -429,13 +424,11 @@
             .Times(1);
 
     mDisplay->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                                           ui::RenderIntent::TONE_MAP_COLORIMETRIC,
-                                           ui::Dataspace::UNKNOWN});
+                                           ui::RenderIntent::TONE_MAP_COLORIMETRIC});
 
     EXPECT_EQ(ui::ColorMode::DISPLAY_P3, mDisplay->getState().colorMode);
     EXPECT_EQ(ui::Dataspace::DISPLAY_P3, mDisplay->getState().dataspace);
     EXPECT_EQ(ui::RenderIntent::TONE_MAP_COLORIMETRIC, mDisplay->getState().renderIntent);
-    EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().targetDataspace);
 }
 
 TEST_F(DisplaySetColorModeTest, doesNothingForVirtualDisplay) {
@@ -448,19 +441,13 @@
     virtualDisplay->setDisplayColorProfileForTest(
             std::unique_ptr<DisplayColorProfile>(colorProfile));
 
-    EXPECT_CALL(*colorProfile,
-                getTargetDataspace(ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                                   ui::Dataspace::UNKNOWN))
-            .WillOnce(Return(ui::Dataspace::UNKNOWN));
-
-    virtualDisplay->setColorProfile(
-            ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                         ui::RenderIntent::TONE_MAP_COLORIMETRIC, ui::Dataspace::UNKNOWN});
+    virtualDisplay->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3,
+                                                 ui::Dataspace::DISPLAY_P3,
+                                                 ui::RenderIntent::TONE_MAP_COLORIMETRIC});
 
     EXPECT_EQ(ui::ColorMode::NATIVE, virtualDisplay->getState().colorMode);
     EXPECT_EQ(ui::Dataspace::UNKNOWN, virtualDisplay->getState().dataspace);
     EXPECT_EQ(ui::RenderIntent::COLORIMETRIC, virtualDisplay->getState().renderIntent);
-    EXPECT_EQ(ui::Dataspace::UNKNOWN, virtualDisplay->getState().targetDataspace);
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 67b94ee..892bb8f 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -94,7 +94,8 @@
     MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
     MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync));
     MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
-    MOCK_CONST_METHOD1(getModes, std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId));
+    MOCK_CONST_METHOD2(getModes,
+                       std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId, int32_t));
     MOCK_CONST_METHOD1(getActiveMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
     MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId));
     MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index aa83883..1c54469 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -334,7 +334,7 @@
 
 TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame) {
     const int kShadowRadius = 5;
-    mLayerFEState.shadowRadius = kShadowRadius;
+    mLayerFEState.shadowSettings.length = kShadowRadius;
     mLayerFEState.forceClientComposition = true;
 
     mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
@@ -345,7 +345,7 @@
 
 TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame_onlyIfForcingClientComposition) {
     const int kShadowRadius = 5;
-    mLayerFEState.shadowRadius = kShadowRadius;
+    mLayerFEState.shadowSettings.length = kShadowRadius;
     mLayerFEState.forceClientComposition = false;
 
     mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
@@ -642,7 +642,7 @@
 
 TEST_F(OutputLayerUpdateCompositionStateTest, setsOutputLayerColorspaceCorrectly) {
     mLayerFEState.dataspace = ui::Dataspace::DISPLAY_P3;
-    mOutputState.targetDataspace = ui::Dataspace::V0_SCRGB;
+    mOutputState.dataspace = ui::Dataspace::V0_SCRGB;
 
     // If the layer is not colorspace agnostic, the output layer dataspace
     // should use the layers requested colorspace.
@@ -659,11 +659,18 @@
     mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
 
     EXPECT_EQ(ui::Dataspace::V0_SCRGB, mOutputLayer.getState().dataspace);
+
+    // If the output is HDR, then don't blind the user with a colorspace agnostic dataspace
+    // drawing all white
+    mOutputState.dataspace = ui::Dataspace::BT2020_PQ;
+
+    mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
+
+    EXPECT_EQ(ui::Dataspace::DISPLAY_P3, mOutputLayer.getState().dataspace);
 }
 
 TEST_F(OutputLayerUpdateCompositionStateTest, setsOutputLayerColorspaceWith170mReplacement) {
     mLayerFEState.dataspace = ui::Dataspace::TRANSFER_SMPTE_170M;
-    mOutputState.targetDataspace = ui::Dataspace::V0_SCRGB;
     mOutputState.treat170mAsSrgb = false;
     mLayerFEState.isColorspaceAgnostic = false;
 
@@ -1607,5 +1614,20 @@
     EXPECT_TRUE(mOutputLayer.needsFiltering());
 }
 
+TEST_F(OutputLayerTest, needsFilteringReturnsFalseIfRotatedDisplaySizeSameAsSourceSize) {
+    mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+    mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+    mOutputLayer.editState().bufferTransform = Hwc2::Transform::ROT_90;
+
+    EXPECT_FALSE(mOutputLayer.needsFiltering());
+}
+
+TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfRotatedDisplaySizeDiffersFromSourceSize) {
+    mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+    mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+
+    EXPECT_TRUE(mOutputLayer.needsFiltering());
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 9e0e7b5..cdcb68d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -175,12 +175,10 @@
 using ColorProfile = compositionengine::Output::ColorProfile;
 
 void dumpColorProfile(ColorProfile profile, std::string& result, const char* name) {
-    android::base::StringAppendF(&result, "%s (%s[%d] %s[%d] %s[%d] %s[%d]) ", name,
+    android::base::StringAppendF(&result, "%s (%s[%d] %s[%d] %s[%d]) ", name,
                                  toString(profile.mode).c_str(), profile.mode,
                                  toString(profile.dataspace).c_str(), profile.dataspace,
-                                 toString(profile.renderIntent).c_str(), profile.renderIntent,
-                                 toString(profile.colorSpaceAgnosticDataspace).c_str(),
-                                 profile.colorSpaceAgnosticDataspace);
+                                 toString(profile.renderIntent).c_str(), profile.renderIntent);
 }
 
 // Checks for a ColorProfile match
@@ -192,8 +190,7 @@
     *result_listener << buf;
 
     return (expected.mode == arg.mode) && (expected.dataspace == arg.dataspace) &&
-            (expected.renderIntent == arg.renderIntent) &&
-            (expected.colorSpaceAgnosticDataspace == arg.colorSpaceAgnosticDataspace);
+            (expected.renderIntent == arg.renderIntent);
 }
 
 /*
@@ -540,20 +537,14 @@
 TEST_F(OutputSetColorProfileTest, setsStateAndDirtiesOutputIfChanged) {
     using ColorProfile = Output::ColorProfile;
 
-    EXPECT_CALL(*mDisplayColorProfile,
-                getTargetDataspace(ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                                   ui::Dataspace::UNKNOWN))
-            .WillOnce(Return(ui::Dataspace::UNKNOWN));
     EXPECT_CALL(*mRenderSurface, setBufferDataspace(ui::Dataspace::DISPLAY_P3)).Times(1);
 
     mOutput->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                                          ui::RenderIntent::TONE_MAP_COLORIMETRIC,
-                                          ui::Dataspace::UNKNOWN});
+                                          ui::RenderIntent::TONE_MAP_COLORIMETRIC});
 
     EXPECT_EQ(ui::ColorMode::DISPLAY_P3, mOutput->getState().colorMode);
     EXPECT_EQ(ui::Dataspace::DISPLAY_P3, mOutput->getState().dataspace);
     EXPECT_EQ(ui::RenderIntent::TONE_MAP_COLORIMETRIC, mOutput->getState().renderIntent);
-    EXPECT_EQ(ui::Dataspace::UNKNOWN, mOutput->getState().targetDataspace);
 
     EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region(kDefaultDisplaySize)));
 }
@@ -561,19 +552,12 @@
 TEST_F(OutputSetColorProfileTest, doesNothingIfNoChange) {
     using ColorProfile = Output::ColorProfile;
 
-    EXPECT_CALL(*mDisplayColorProfile,
-                getTargetDataspace(ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                                   ui::Dataspace::UNKNOWN))
-            .WillOnce(Return(ui::Dataspace::UNKNOWN));
-
     mOutput->editState().colorMode = ui::ColorMode::DISPLAY_P3;
     mOutput->editState().dataspace = ui::Dataspace::DISPLAY_P3;
     mOutput->editState().renderIntent = ui::RenderIntent::TONE_MAP_COLORIMETRIC;
-    mOutput->editState().targetDataspace = ui::Dataspace::UNKNOWN;
 
     mOutput->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
-                                          ui::RenderIntent::TONE_MAP_COLORIMETRIC,
-                                          ui::Dataspace::UNKNOWN});
+                                          ui::RenderIntent::TONE_MAP_COLORIMETRIC});
 
     EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region()));
 }
@@ -1846,7 +1830,7 @@
     ui::Transform translate;
     translate.set(50, 50);
     mLayer.layerFEState.geomLayerTransform = translate;
-    mLayer.layerFEState.shadowRadius = 10.0f;
+    mLayer.layerFEState.shadowSettings.length = 10.0f;
 
     mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
     // half of the layer including the casting shadow is covered and opaque
@@ -1888,7 +1872,7 @@
     ui::Transform translate;
     translate.set(50, 50);
     mLayer.layerFEState.geomLayerTransform = translate;
-    mLayer.layerFEState.shadowRadius = 10.0f;
+    mLayer.layerFEState.shadowSettings.length = 10.0f;
 
     mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
     // Casting layer is covered by an opaque region leaving only part of its shadow to be drawn
@@ -1913,7 +1897,7 @@
     ui::Transform translate;
     translate.set(50, 50);
     mLayer.layerFEState.geomLayerTransform = translate;
-    mLayer.layerFEState.shadowRadius = 10.0f;
+    mLayer.layerFEState.shadowSettings.length = 10.0f;
 
     mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
     // Casting layer and its shadows are covered by an opaque region
@@ -2133,12 +2117,11 @@
 
     EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3u));
     EXPECT_CALL(mOutput,
-                setColorProfile(ColorProfileEq(
-                        ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
-                                     ui::RenderIntent::COLORIMETRIC, ui::Dataspace::UNKNOWN})));
+                setColorProfile(
+                        ColorProfileEq(ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+                                                    ui::RenderIntent::COLORIMETRIC})));
 
     mRefreshArgs.outputColorSetting = OutputColorSetting::kUnmanaged;
-    mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
 
     mOutput.updateColorProfile(mRefreshArgs);
 }
@@ -2148,7 +2131,6 @@
     OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile() {
         EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
         mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
-        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
     }
 
     struct ExpectBestColorModeCallResultUsedToSetColorProfileState
@@ -2163,8 +2145,7 @@
                                     SetArgPointee<4>(renderIntent)));
             EXPECT_CALL(getInstance()->mOutput,
                         setColorProfile(
-                                ColorProfileEq(ColorProfile{colorMode, dataspace, renderIntent,
-                                                            ui::Dataspace::UNKNOWN})));
+                                ColorProfileEq(ColorProfile{colorMode, dataspace, renderIntent})));
             return nextState<ExecuteState>();
         }
     };
@@ -2191,55 +2172,6 @@
             .execute();
 }
 
-struct OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile
-      : public OutputUpdateColorProfileTest {
-    OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile() {
-        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
-        EXPECT_CALL(*mDisplayColorProfile,
-                    getBestColorMode(ui::Dataspace::V0_SRGB, ui::RenderIntent::ENHANCE, _, _, _))
-                .WillRepeatedly(DoAll(SetArgPointee<2>(ui::Dataspace::UNKNOWN),
-                                      SetArgPointee<3>(ui::ColorMode::NATIVE),
-                                      SetArgPointee<4>(ui::RenderIntent::COLORIMETRIC)));
-        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
-    }
-
-    struct IfColorSpaceAgnosticDataspaceSetToState
-          : public CallOrderStateMachineHelper<TestType, IfColorSpaceAgnosticDataspaceSetToState> {
-        [[nodiscard]] auto ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace dataspace) {
-            getInstance()->mRefreshArgs.colorSpaceAgnosticDataspace = dataspace;
-            return nextState<ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState>();
-        }
-    };
-
-    struct ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState
-          : public CallOrderStateMachineHelper<
-                    TestType, ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState> {
-        [[nodiscard]] auto thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(
-                ui::Dataspace dataspace) {
-            EXPECT_CALL(getInstance()->mOutput,
-                        setColorProfile(ColorProfileEq(
-                                ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
-                                             ui::RenderIntent::COLORIMETRIC, dataspace})));
-            return nextState<ExecuteState>();
-        }
-    };
-
-    // Call this member function to start using the mini-DSL defined above.
-    [[nodiscard]] auto verify() { return IfColorSpaceAgnosticDataspaceSetToState::make(this); }
-};
-
-TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, DisplayP3) {
-    verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::DISPLAY_P3)
-            .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::DISPLAY_P3)
-            .execute();
-}
-
-TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, V0_SRGB) {
-    verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::V0_SRGB)
-            .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::V0_SRGB)
-            .execute();
-}
-
 struct OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference
       : public OutputUpdateColorProfileTest {
     // Internally the implementation looks through the dataspaces of all the
@@ -2248,7 +2180,6 @@
 
     OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference() {
         mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
-        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
 
         EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3u));
         EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
@@ -2368,7 +2299,6 @@
 
     OutputUpdateColorProfileTest_ForceOutputColorOverrides() {
         mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
-        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
 
         mLayer1.mLayerFEState.dataspace = ui::Dataspace::DISPLAY_BT2020;
 
@@ -2424,7 +2354,6 @@
 struct OutputUpdateColorProfileTest_Hdr : public OutputUpdateColorProfileTest {
     OutputUpdateColorProfileTest_Hdr() {
         mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
-        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
         EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
         EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
     }
@@ -2703,7 +2632,6 @@
 
     OutputUpdateColorProfile_AffectsChosenRenderIntentTest() {
         mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
-        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
         mLayer1.mLayerFEState.dataspace = ui::Dataspace::BT2020_PQ;
         EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u));
         EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
@@ -3493,10 +3421,10 @@
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
                 return ftl::yield<FenceResult>(Fence::NO_FENCE);
             });
@@ -3524,10 +3452,10 @@
                     }));
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
                 return ftl::yield<FenceResult>(Fence::NO_FENCE);
             });
@@ -3558,10 +3486,10 @@
                     }));
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, true, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
                 return ftl::yield<FenceResult>(Fence::NO_FENCE);
             });
@@ -3587,7 +3515,7 @@
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .Times(2)
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
@@ -3617,7 +3545,7 @@
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false));
 
@@ -3653,10 +3581,10 @@
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
             .WillOnce(Return(mOutputBuffer))
             .WillOnce(Return(otherOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
                 return ftl::yield<FenceResult>(Fence::NO_FENCE);
             });
@@ -3688,9 +3616,9 @@
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     verify().execute().expectAFenceWasReturned();
@@ -3767,7 +3695,7 @@
     struct ExpectDisplaySettingsState
           : public CallOrderStateMachineHelper<TestType, ExpectDisplaySettingsState> {
         auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) {
-            EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _))
+            EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, _))
                     .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
             return nextState<ExecuteState>();
         }
@@ -4020,11 +3948,11 @@
         EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
                 .WillRepeatedly(Return());
         EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
                 .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    const bool, base::unique_fd&&) -> ftl::Future<FenceResult> {
+                                    base::unique_fd&&) -> ftl::Future<FenceResult> {
                     return ftl::yield<FenceResult>(Fence::NO_FENCE);
                 });
     }
@@ -4059,7 +3987,7 @@
     EXPECT_CALL(*mRenderSurface, setProtected(true));
     // Must happen after setting the protected content state.
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     base::unique_fd fd;
@@ -4118,7 +4046,7 @@
     InSequence seq;
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     base::unique_fd fd;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index bd030d0..d61d7ba 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -353,7 +353,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
                                 const std::vector<renderengine::LayerSettings>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
         EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
@@ -369,7 +369,7 @@
             .WillOnce(Return(clientComp1));
     EXPECT_CALL(*layerFE2, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(false)))
             .WillOnce(Return(clientComp2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = false;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
@@ -402,7 +402,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
                                 const std::vector<renderengine::LayerSettings>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
         EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
@@ -419,7 +419,7 @@
             .WillOnce(Return(clientComp1));
     EXPECT_CALL(*layerFE2, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(true)))
             .WillOnce(Return(clientComp2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = true;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
@@ -452,7 +452,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
                                 const std::vector<renderengine::LayerSettings>&,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
         return ftl::yield<FenceResult>(Fence::NO_FENCE);
@@ -466,7 +466,7 @@
                 prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq(
                         mOutputState.displayBrightnessNits)))
             .WillOnce(Return(clientComp2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = true;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
@@ -502,7 +502,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
                                 const std::vector<renderengine::LayerSettings>&,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
         return ftl::yield<FenceResult>(Fence::NO_FENCE);
@@ -516,7 +516,7 @@
                 prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq(
                         mOutputState.displayBrightnessNits)))
             .WillOnce(Return(clientComp2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = true;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, false);
     expectReadyBuffer(cachedSet);
@@ -551,7 +551,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
                                 const std::vector<renderengine::LayerSettings>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
         EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
@@ -566,7 +566,7 @@
 
     EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1));
     EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
 
@@ -815,7 +815,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         // If the highlight layer is enabled, it will increase the size by 1.
         // We're interested in the third layer either way.
@@ -839,7 +839,7 @@
         return ftl::yield<FenceResult>(Fence::NO_FENCE);
     };
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
 }
 
@@ -875,7 +875,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         // If the highlight layer is enabled, it will increase the size by 1.
         // We're interested in the third layer either way.
@@ -900,7 +900,7 @@
         return ftl::yield<FenceResult>(Fence::NO_FENCE);
     };
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
 }
 
@@ -1026,7 +1026,7 @@
 
     const auto drawLayers = [&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
         // If the highlight layer is enabled, it will increase the size by 1.
         // We're interested in the third layer either way.
@@ -1039,7 +1039,7 @@
         return ftl::yield<FenceResult>(Fence::NO_FENCE);
     };
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 778a0a8..00590e6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -168,7 +168,7 @@
 
 void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     initializeOverrideBuffer(layers);
@@ -419,7 +419,7 @@
     // caleed for Layer2 and Layer3
     layerState1->resetFramesSinceBufferUpdate();
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -442,7 +442,7 @@
     layerState1->incrementFramesSinceBufferUpdate();
     mTime += 200ms;
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
@@ -494,7 +494,7 @@
     // called for Layer1 and Layer2
     layerState3->resetFramesSinceBufferUpdate();
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -508,7 +508,7 @@
     EXPECT_EQ(nullptr, overrideBuffer5);
 
     // Layers 1 and 2 will be flattened a new drawFrame would be called for Layer4 and Layer5
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
@@ -537,7 +537,7 @@
 
     layerState3->incrementFramesSinceBufferUpdate();
     mTime += 200ms;
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
@@ -592,7 +592,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -603,7 +603,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -656,7 +656,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -667,7 +667,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -728,7 +728,7 @@
 
     // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
     // exception that there would be a hole punch above it.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -736,7 +736,7 @@
     EXPECT_EQ(nullptr, overrideBuffer0);
 
     // This time we merge the CachedSet in and we should still have only two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -798,7 +798,7 @@
 
     // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
     // exception that there would be a hole punch above it.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -806,7 +806,7 @@
     EXPECT_EQ(nullptr, overrideBuffer0);
 
     // This time we merge the CachedSet in and we should still have only two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -849,7 +849,7 @@
     layerState3->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     initializeOverrideBuffer(layers);
@@ -894,7 +894,7 @@
     layerState1->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillRepeatedly(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     initializeOverrideBuffer(layers);
@@ -947,7 +947,7 @@
     layerState1->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     initializeOverrideBuffer(layers);
@@ -995,7 +995,7 @@
     layerStateWithBlurBehind->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     initializeOverrideBuffer(layers);
@@ -1037,7 +1037,7 @@
     // Mark the layers inactive
     mTime += 200ms;
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
 
     initializeOverrideBuffer(layers);
@@ -1050,7 +1050,7 @@
 
     // Simulate attempting to render prior to merging the new cached set with the layer stack.
     // Here we should not try to re-render.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We provide the override buffer now that it's rendered
@@ -1097,14 +1097,14 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     for (size_t i = 0; i < kMaxDeferRenderAttempts; i++) {
-        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
         mFlattener->renderCachedSets(mOutputState,
                                      std::chrono::steady_clock::now() -
                                              (kCachedSetRenderDuration + 10ms),
                                      true);
     }
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState,
                                  std::chrono::steady_clock::now() -
@@ -1139,7 +1139,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -1150,7 +1150,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1189,7 +1189,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -1200,7 +1200,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1239,7 +1239,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -1250,7 +1250,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1289,7 +1289,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -1300,7 +1300,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1342,7 +1342,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -1354,7 +1354,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1394,7 +1394,7 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
             .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
@@ -1405,7 +1405,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 195d90c..5b6591a 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -37,10 +37,10 @@
 #include <configstore/Utils.h>
 #include <log/log.h>
 #include <system/window.h>
-#include <ui/GraphicTypes.h>
 
 #include "DisplayDevice.h"
 #include "FrontEnd/DisplayInfo.h"
+#include "HdrSdrRatioOverlay.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 #include "SurfaceFlinger.h"
@@ -208,12 +208,12 @@
     return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
 }
 
-void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps displayFps, Fps renderFps) {
-    ATRACE_INT(mActiveModeFPSTrace.c_str(), displayFps.getIntValue());
+void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
+    ATRACE_INT(mActiveModeFPSTrace.c_str(), vsyncRate.getIntValue());
     ATRACE_INT(mRenderFrameRateFPSTrace.c_str(), renderFps.getIntValue());
 
     mRefreshRateSelector->setActiveMode(modeId, renderFps);
-    updateRefreshRateOverlayRate(displayFps, renderFps);
+    updateRefreshRateOverlayRate(vsyncRate, renderFps);
 }
 
 status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info,
@@ -230,14 +230,14 @@
     mIsModeSetPending = true;
 
     const auto& pendingMode = *info.modeOpt->modePtr;
-    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), pendingMode.getFps().getIntValue());
+    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), pendingMode.getVsyncRate().getIntValue());
 
     return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), pendingMode.getHwcId(),
                                                     constraints, outTimeline);
 }
 
-void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps displayFps, Fps renderFps) {
-    setActiveMode(modeId, displayFps, renderFps);
+void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
+    setActiveMode(modeId, vsyncRate, renderFps);
     mIsModeSetPending = false;
 }
 
@@ -253,7 +253,7 @@
         return vsyncPeriod;
     }
 
-    return refreshRateSelector().getActiveMode().modePtr->getVsyncPeriod();
+    return refreshRateSelector().getActiveMode().modePtr->getVsyncRate().getPeriodNsecs();
 }
 
 ui::Dataspace DisplayDevice::getCompositionDataSpace() const {
@@ -265,6 +265,9 @@
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setLayerStack(filter.layerStack);
     }
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->setLayerStack(filter.layerStack);
+    }
 }
 
 void DisplayDevice::setFlags(uint32_t flags) {
@@ -278,10 +281,14 @@
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setViewport(size);
     }
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->setViewport(size);
+    }
 }
 
 void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpaceRect,
                                   Rect orientedDisplaySpaceRect) {
+    mIsOrientationChanged = mOrientation != orientation;
     mOrientation = orientation;
 
     // We need to take care of display rotation for globalTransform for case if the panel is not
@@ -415,6 +422,28 @@
                            capabilities.getDesiredMinLuminance());
 }
 
+void DisplayDevice::enableHdrSdrRatioOverlay(bool enable) {
+    if (!enable) {
+        mHdrSdrRatioOverlay.reset();
+        return;
+    }
+
+    mHdrSdrRatioOverlay = HdrSdrRatioOverlay::create();
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->setLayerStack(getLayerStack());
+        mHdrSdrRatioOverlay->setViewport(getSize());
+        updateHdrSdrRatioOverlayRatio(mHdrSdrRatio);
+    }
+}
+
+void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
+    ATRACE_CALL();
+    mHdrSdrRatio = currentHdrSdrRatio;
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
+    }
+}
+
 void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner,
                                              bool showRenderRate, bool showInMiddle) {
     if (!enable) {
@@ -439,17 +468,25 @@
         features |= RefreshRateOverlay::Features::SetByHwc;
     }
 
+    // TODO(b/296636258) Update to use the render rate range in VRR mode.
     const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
-    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features);
-    mRefreshRateOverlay->setLayerStack(getLayerStack());
-    mRefreshRateOverlay->setViewport(getSize());
-    updateRefreshRateOverlayRate(getActiveMode().modePtr->getFps(), getActiveMode().fps, setByHwc);
+    mRefreshRateOverlay = RefreshRateOverlay::create(fpsRange, features);
+    if (mRefreshRateOverlay) {
+        mRefreshRateOverlay->setLayerStack(getLayerStack());
+        mRefreshRateOverlay->setViewport(getSize());
+        updateRefreshRateOverlayRate(getActiveMode().modePtr->getVsyncRate(), getActiveMode().fps,
+                                     setByHwc);
+    }
 }
 
-void DisplayDevice::updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc) {
+void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) {
     ATRACE_CALL();
-    if (mRefreshRateOverlay && (!mRefreshRateOverlay->isSetByHwc() || setByHwc)) {
-        mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps);
+    if (mRefreshRateOverlay) {
+        if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
+            mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps);
+        } else {
+            mRefreshRateOverlay->changeRenderRate(renderFps);
+        }
     }
 }
 
@@ -459,7 +496,7 @@
         const auto newMode =
                 mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired);
         if (newMode) {
-            updateRefreshRateOverlayRate(newMode->modePtr->getFps(), newMode->fps);
+            updateRefreshRateOverlayRate(newMode->modePtr->getVsyncRate(), newMode->fps);
             return true;
         }
     }
@@ -467,10 +504,23 @@
     return false;
 }
 
-void DisplayDevice::animateRefreshRateOverlay() {
+void DisplayDevice::animateOverlay() {
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->animate();
     }
+    if (mHdrSdrRatioOverlay) {
+        // hdr sdr ratio is designed to be on the top right of the screen,
+        // therefore, we need to re-calculate the display's width and height
+        if (mIsOrientationChanged) {
+            auto width = getWidth();
+            auto height = getHeight();
+            if (mOrientation == ui::ROTATION_90 || mOrientation == ui::ROTATION_270) {
+                std::swap(width, height);
+            }
+            mHdrSdrRatioOverlay->setViewport({width, height});
+        }
+        mHdrSdrRatioOverlay->animate();
+    }
 }
 
 auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force)
@@ -501,14 +551,14 @@
             return DesiredActiveModeAction::None;
         }
 
-        setActiveMode(desiredMode.getId(), desiredMode.getFps(), info.modeOpt->fps);
+        setActiveMode(desiredMode.getId(), desiredMode.getVsyncRate(), info.modeOpt->fps);
         return DesiredActiveModeAction::InitiateRenderRateSwitch;
     }
 
     // Set the render frame rate to the current physical refresh rate to schedule the next
     // frame as soon as possible.
-    setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getFps(),
-                  currentMode.modePtr->getFps());
+    setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getVsyncRate(),
+                  currentMode.modePtr->getVsyncRate());
 
     // Initiate a mode change.
     mDesiredActiveModeChanged = true;
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 6d2fe54..a044534 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -55,6 +55,7 @@
 
 class Fence;
 class HWComposer;
+class HdrSdrRatioOverlay;
 class IGraphicBufferProducer;
 class Layer;
 class RefreshRateOverlay;
@@ -223,14 +224,14 @@
         return mRefreshRateSelector->getActiveMode();
     }
 
-    void setActiveMode(DisplayModeId, Fps displayFps, Fps renderFps);
+    void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
 
     status_t initiateModeChange(const ActiveModeInfo&,
                                 const hal::VsyncPeriodChangeConstraints& constraints,
                                 hal::VsyncPeriodChangeTimeline* outTimeline)
             REQUIRES(kMainThreadContext);
 
-    void finalizeModeChange(DisplayModeId, Fps displayFps, Fps renderFps)
+    void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
             REQUIRES(kMainThreadContext);
 
     scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; }
@@ -240,13 +241,19 @@
         return mRefreshRateSelector;
     }
 
+    void animateOverlay();
+
     // Enables an overlay to be displayed with the current refresh rate
     void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
                                   bool showInMiddle) REQUIRES(kMainThreadContext);
-    void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false);
+    void updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
-    void animateRefreshRateOverlay();
+
+    // Enables an overlay to be display with the hdr/sdr ratio
+    void enableHdrSdrRatioOverlay(bool enable) REQUIRES(kMainThreadContext);
+    void updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio);
+    bool isHdrSdrRatioOverlayEnabled() const { return mHdrSdrRatioOverlay != nullptr; }
 
     nsecs_t getVsyncPeriodFromHWC() const;
 
@@ -276,6 +283,7 @@
 
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
+    bool mIsOrientationChanged = false;
 
     // Allow nullopt as initial power mode.
     using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
@@ -302,6 +310,9 @@
 
     std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
     std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay;
+    std::unique_ptr<HdrSdrRatioOverlay> mHdrSdrRatioOverlay;
+    // This parameter is only used for hdr/sdr ratio overlay
+    float mHdrSdrRatio = 1.0f;
 
     mutable std::mutex mActiveModeLock;
     ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index c0eb36d..1f409c6 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -244,14 +244,13 @@
     addReader(translate<Display>(kSingleReaderKey));
 
     // If unable to read interface version, then become backwards compatible.
-    int32_t version = 1;
-    const auto status = mAidlComposerClient->getInterfaceVersion(&version);
+    const auto status = mAidlComposerClient->getInterfaceVersion(&mComposerInterfaceVersion);
     if (!status.isOk()) {
         ALOGE("getInterfaceVersion for AidlComposer constructor failed %s",
               status.getDescription().c_str());
     }
-    mSupportsBufferSlotsToClear = version > 1;
-    if (!mSupportsBufferSlotsToClear) {
+
+    if (mComposerInterfaceVersion <= 1) {
         if (sysprop::clear_slots_with_set_layer_buffer(false)) {
             mClearSlotBuffer = sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBX_8888,
                                                        GraphicBuffer::USAGE_HW_COMPOSER |
@@ -281,6 +280,10 @@
     }
 }
 
+bool AidlComposer::getDisplayConfigurationsSupported() const {
+    return mComposerInterfaceVersion >= 3;
+}
+
 std::vector<Capability> AidlComposer::getCapabilities() {
     std::vector<Capability> capabilities;
     const auto status = mAidlComposer->getCapabilities(&capabilities);
@@ -489,6 +492,19 @@
     return Error::NONE;
 }
 
+Error AidlComposer::getDisplayConfigurations(Display display, int32_t maxFrameIntervalNs,
+                                             std::vector<DisplayConfiguration>* outConfigs) {
+    const auto status =
+            mAidlComposerClient->getDisplayConfigurations(translate<int64_t>(display),
+                                                          maxFrameIntervalNs, outConfigs);
+    if (!status.isOk()) {
+        ALOGE("getDisplayConfigurations failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    return Error::NONE;
+}
+
 Error AidlComposer::getDisplayName(Display display, std::string* outName) {
     const auto status = mAidlComposerClient->getDisplayName(translate<int64_t>(display), outName);
     if (!status.isOk()) {
@@ -848,7 +864,7 @@
     Error error = Error::NONE;
     mMutex.lock_shared();
     if (auto writer = getWriter(display)) {
-        if (mSupportsBufferSlotsToClear) {
+        if (mComposerInterfaceVersion > 1) {
             writer->get().setLayerBufferSlotsToClear(translate<int64_t>(display),
                                                      translate<int64_t>(layer), slotsToClear);
             // Backwards compatible way of clearing buffer slots is to set the layer buffer with a
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 8d21b49..b1b57a4 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -66,6 +66,7 @@
     ~AidlComposer() override;
 
     bool isSupported(OptionalFeature) const;
+    bool getDisplayConfigurationsSupported() const;
 
     std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
             override;
@@ -95,6 +96,8 @@
     Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
                               int32_t* outValue) override;
     Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
+    Error getDisplayConfigurations(Display, int32_t maxFrameIntervalNs,
+                                   std::vector<DisplayConfiguration>*);
     Error getDisplayName(Display display, std::string* outName) override;
 
     Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
@@ -285,8 +288,8 @@
     // threading annotations.
     ftl::SharedMutex mMutex;
 
-    // Whether or not explicitly clearing buffer slots is supported.
-    bool mSupportsBufferSlotsToClear;
+    int32_t mComposerInterfaceVersion = 1;
+
     // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
     sp<GraphicBuffer> mClearSlotBuffer;
 
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index cf67795..e942587 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -39,6 +39,7 @@
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
 #include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
@@ -85,6 +86,7 @@
 using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
 using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
 using AidlTransform = ::aidl::android::hardware::graphics::common::Transform;
+using DisplayConfiguration = V3_0::DisplayConfiguration;
 using aidl::android::hardware::graphics::common::Hdr;
 
 class Composer {
@@ -103,6 +105,7 @@
     };
 
     virtual bool isSupported(OptionalFeature) const = 0;
+    virtual bool getDisplayConfigurationsSupported() const = 0;
 
     virtual std::vector<aidl::android::hardware::graphics::composer3::Capability>
     getCapabilities() = 0;
@@ -130,6 +133,10 @@
     virtual Error getDisplayAttribute(Display display, Config config,
                                       IComposerClient::Attribute attribute, int32_t* outValue) = 0;
     virtual Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs) = 0;
+
+    virtual Error getDisplayConfigurations(Display, int32_t maxFrameIntervalNs,
+                                           std::vector<DisplayConfiguration>*) = 0;
+
     virtual Error getDisplayName(Display display, std::string* outName) = 0;
 
     virtual Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 61a9a08..1775a7a 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -31,6 +31,7 @@
 
 #include "DisplayHardware/Hal.h"
 #include "Scheduler/StrongTyping.h"
+#include "Utils/FlagUtils.h"
 
 namespace android {
 
@@ -49,6 +50,7 @@
 
 using DisplayModes = ftl::SmallMap<DisplayModeId, DisplayModePtr, 3>;
 using DisplayModeIterator = DisplayModes::const_iterator;
+using namespace com::android::graphics::surfaceflinger;
 
 class DisplayMode {
 public:
@@ -76,24 +78,29 @@
         }
 
         Builder& setVsyncPeriod(nsecs_t vsyncPeriod) {
-            mDisplayMode->mFps = Fps::fromPeriodNsecs(vsyncPeriod);
+            mDisplayMode->mVsyncRate = Fps::fromPeriodNsecs(vsyncPeriod);
             return *this;
         }
 
-        Builder& setDpiX(int32_t dpiX) {
-            if (dpiX == -1) {
+        Builder& setVrrConfig(std::optional<hal::VrrConfig> vrrConfig) {
+            mDisplayMode->mVrrConfig = std::move(vrrConfig);
+            return *this;
+        }
+
+        Builder& setDpiX(float dpiX) {
+            if (dpiX == -1.f) {
                 mDisplayMode->mDpi.x = getDefaultDensity();
             } else {
-                mDisplayMode->mDpi.x = dpiX / 1000.f;
+                mDisplayMode->mDpi.x = dpiX;
             }
             return *this;
         }
 
-        Builder& setDpiY(int32_t dpiY) {
-            if (dpiY == -1) {
+        Builder& setDpiY(float dpiY) {
+            if (dpiY == -1.f) {
                 mDisplayMode->mDpi.y = getDefaultDensity();
             } else {
-                mDisplayMode->mDpi.y = dpiY / 1000.f;
+                mDisplayMode->mDpi.y = dpiY;
             }
             return *this;
         }
@@ -130,8 +137,17 @@
     int32_t getWidth() const { return mResolution.getWidth(); }
     int32_t getHeight() const { return mResolution.getHeight(); }
 
-    Fps getFps() const { return mFps; }
-    nsecs_t getVsyncPeriod() const { return mFps.getPeriodNsecs(); }
+    // Peak refresh rate represents the highest refresh rate that can be used
+    // for the presentation.
+    Fps getPeakFps() const {
+        return flagutils::vrrConfigEnabled() && mVrrConfig
+                ? Fps::fromPeriodNsecs(mVrrConfig->minFrameIntervalNs)
+                : mVsyncRate;
+    }
+
+    Fps getVsyncRate() const { return mVsyncRate; }
+
+    std::optional<hal::VrrConfig> getVrrConfig() const { return mVrrConfig; }
 
     struct Dpi {
         float x = -1;
@@ -155,23 +171,25 @@
     PhysicalDisplayId mPhysicalDisplayId;
 
     ui::Size mResolution;
-    Fps mFps;
+    Fps mVsyncRate;
     Dpi mDpi;
     int32_t mGroup = -1;
+    std::optional<hal::VrrConfig> mVrrConfig;
 };
 
 inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& rhs) {
     return lhs.getHwcId() == rhs.getHwcId() && lhs.getResolution() == rhs.getResolution() &&
-            lhs.getVsyncPeriod() == rhs.getVsyncPeriod() && lhs.getDpi() == rhs.getDpi() &&
-            lhs.getGroup() == rhs.getGroup();
+            lhs.getVsyncRate().getPeriodNsecs() == rhs.getVsyncRate().getPeriodNsecs() &&
+            lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup();
 }
 
 inline std::string to_string(const DisplayMode& mode) {
-    return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, refreshRate=%s, "
-                              "dpi=%.2fx%.2f, group=%d}",
+    return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, "
+                              "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}",
                               mode.getId().value(), mode.getHwcId(), mode.getWidth(),
-                              mode.getHeight(), to_string(mode.getFps()).c_str(), mode.getDpi().x,
-                              mode.getDpi().y, mode.getGroup());
+                              mode.getHeight(), to_string(mode.getVsyncRate()).c_str(),
+                              mode.getDpi().x, mode.getDpi().y, mode.getGroup(),
+                              to_string(mode.getVrrConfig()).c_str());
 }
 
 template <typename... DisplayModePtrs>
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index aaf2523..0c2b77d 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -311,6 +311,14 @@
 }
 
 Error Display::supportsDoze(bool* outSupport) const {
+    {
+        std::scoped_lock lock(mDisplayCapabilitiesMutex);
+        if (!mDisplayCapabilities) {
+            // The display has not turned on since boot, so DOZE support is unknown.
+            ALOGW("%s: haven't queried capabilities yet!", __func__);
+            return Error::NO_RESOURCES;
+        }
+    }
     *outSupport = hasCapability(DisplayCapability::DOZE);
     return Error::NONE;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index f350eba..812621f 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -261,10 +261,50 @@
     return mDisplayData.count(displayId) && mDisplayData.at(displayId).hwcDisplay->isConnected();
 }
 
-std::vector<HWComposer::HWCDisplayMode> HWComposer::getModes(PhysicalDisplayId displayId) const {
+std::vector<HWComposer::HWCDisplayMode> HWComposer::getModes(PhysicalDisplayId displayId,
+                                                             int32_t maxFrameIntervalNs) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     const auto hwcDisplayId = mDisplayData.at(displayId).hwcDisplay->getId();
+
+    if (mComposer->getDisplayConfigurationsSupported()) {
+        return getModesFromDisplayConfigurations(hwcDisplayId, maxFrameIntervalNs);
+    }
+
+    return getModesFromLegacyDisplayConfigs(hwcDisplayId);
+}
+
+std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromDisplayConfigurations(
+        uint64_t hwcDisplayId, int32_t maxFrameIntervalNs) const {
+    std::vector<hal::DisplayConfiguration> configs;
+    auto error = static_cast<hal::Error>(
+            mComposer->getDisplayConfigurations(hwcDisplayId, maxFrameIntervalNs, &configs));
+    RETURN_IF_HWC_ERROR_FOR("getDisplayConfigurations", error, *toPhysicalDisplayId(hwcDisplayId),
+                            {});
+
+    std::vector<HWCDisplayMode> modes;
+    modes.reserve(configs.size());
+    for (auto config : configs) {
+        auto hwcMode = HWCDisplayMode{.hwcId = static_cast<hal::HWConfigId>(config.configId),
+                                      .width = config.width,
+                                      .height = config.height,
+                                      .vsyncPeriod = config.vsyncPeriod,
+                                      .configGroup = config.configGroup,
+                                      .vrrConfig = config.vrrConfig};
+
+        if (config.dpi) {
+            hwcMode.dpiX = config.dpi->x;
+            hwcMode.dpiY = config.dpi->y;
+        }
+
+        modes.push_back(hwcMode);
+    }
+
+    return modes;
+}
+
+std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromLegacyDisplayConfigs(
+        uint64_t hwcDisplayId) const {
     std::vector<hal::HWConfigId> configIds;
     auto error = static_cast<hal::Error>(mComposer->getDisplayConfigs(hwcDisplayId, &configIds));
     RETURN_IF_HWC_ERROR_FOR("getDisplayConfigs", error, *toPhysicalDisplayId(hwcDisplayId), {});
@@ -272,17 +312,25 @@
     std::vector<HWCDisplayMode> modes;
     modes.reserve(configIds.size());
     for (auto configId : configIds) {
-        modes.push_back(HWCDisplayMode{
+        auto hwcMode = HWCDisplayMode{
                 .hwcId = configId,
                 .width = getAttribute(hwcDisplayId, configId, hal::Attribute::WIDTH),
                 .height = getAttribute(hwcDisplayId, configId, hal::Attribute::HEIGHT),
                 .vsyncPeriod = getAttribute(hwcDisplayId, configId, hal::Attribute::VSYNC_PERIOD),
-                .dpiX = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_X),
-                .dpiY = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_Y),
                 .configGroup = getAttribute(hwcDisplayId, configId, hal::Attribute::CONFIG_GROUP),
-        });
-    }
+        };
 
+        const int32_t dpiX = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_X);
+        const int32_t dpiY = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_Y);
+        if (dpiX != -1) {
+            hwcMode.dpiX = static_cast<float>(dpiX) / 1000.f;
+        }
+        if (dpiY != -1) {
+            hwcMode.dpiY = static_cast<float>(dpiY) / 1000.f;
+        }
+
+        modes.push_back(hwcMode);
+    }
     return modes;
 }
 
@@ -567,19 +615,29 @@
             ALOGV("setPowerMode: Calling HWC %s", to_string(mode).c_str());
             {
                 bool supportsDoze = false;
-                auto error = hwcDisplay->supportsDoze(&supportsDoze);
-                if (error != hal::Error::NONE) {
-                    LOG_HWC_ERROR("supportsDoze", error, displayId);
-                }
+                const auto queryDozeError = hwcDisplay->supportsDoze(&supportsDoze);
 
-                if (!supportsDoze) {
+                // queryDozeError might be NO_RESOURCES, in the case of a display that has never
+                // been turned on. In that case, attempt to set to DOZE anyway.
+                if (!supportsDoze && queryDozeError == hal::Error::NONE) {
                     mode = hal::PowerMode::ON;
                 }
 
-                error = hwcDisplay->setPowerMode(mode);
+                auto error = hwcDisplay->setPowerMode(mode);
                 if (error != hal::Error::NONE) {
                     LOG_HWC_ERROR(("setPowerMode(" + to_string(mode) + ")").c_str(), error,
                                   displayId);
+                    // If the display had never been turned on, so its doze
+                    // support was unknown, it may truly not support doze. Try
+                    // switching it to ON instead.
+                    if (queryDozeError == hal::Error::NO_RESOURCES) {
+                        ALOGD("%s: failed to set %s to %s. Trying again with ON", __func__,
+                              to_string(displayId).c_str(), to_string(mode).c_str());
+                        error = hwcDisplay->setPowerMode(hal::PowerMode::ON);
+                        if (error != hal::Error::NONE) {
+                            LOG_HWC_ERROR("setPowerMode(ON)", error, displayId);
+                        }
+                    }
                 }
             }
             break;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 3702c62..b4d3d28 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -99,14 +99,16 @@
         int32_t width = -1;
         int32_t height = -1;
         nsecs_t vsyncPeriod = -1;
-        int32_t dpiX = -1;
-        int32_t dpiY = -1;
+        float dpiX = -1.f;
+        float dpiY = -1.f;
         int32_t configGroup = -1;
+        std::optional<hal::VrrConfig> vrrConfig;
 
         friend std::ostream& operator<<(std::ostream& os, const HWCDisplayMode& mode) {
             return os << "id=" << mode.hwcId << " res=" << mode.width << "x" << mode.height
                       << " vsyncPeriod=" << mode.vsyncPeriod << " dpi=" << mode.dpiX << "x"
-                      << mode.dpiY << " group=" << mode.configGroup;
+                      << mode.dpiY << " group=" << mode.configGroup
+                      << " vrrConfig=" << to_string(mode.vrrConfig).c_str();
         }
     };
 
@@ -229,7 +231,8 @@
 
     virtual bool isConnected(PhysicalDisplayId) const = 0;
 
-    virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId) const = 0;
+    virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
+                                                 int32_t maxFrameIntervalNs) const = 0;
 
     virtual std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const = 0;
 
@@ -412,7 +415,8 @@
 
     bool isConnected(PhysicalDisplayId) const override;
 
-    std::vector<HWCDisplayMode> getModes(PhysicalDisplayId) const override;
+    std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
+                                         int32_t maxFrameIntervalNs) const override;
 
     std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const override;
 
@@ -501,6 +505,10 @@
     std::optional<DisplayIdentificationInfo> onHotplugDisconnect(hal::HWDisplayId);
     bool shouldIgnoreHotplugConnect(hal::HWDisplayId, bool hasDisplayIdentificationData) const;
 
+    std::vector<HWCDisplayMode> getModesFromDisplayConfigurations(uint64_t hwcDisplayId,
+                                                                  int32_t maxFrameIntervalNs) const;
+    std::vector<HWCDisplayMode> getModesFromLegacyDisplayConfigs(uint64_t hwcDisplayId) const;
+
     int32_t getAttribute(hal::HWDisplayId hwcDisplayId, hal::HWConfigId configId,
                          hal::Attribute attribute) const;
 
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index bf3089f..20f7548 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -23,6 +23,8 @@
 #include <aidl/android/hardware/graphics/common/Hdr.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
+#include <aidl/android/hardware/graphics/composer3/VrrConfig.h>
 
 #define ERROR_HAS_CHANGES 5
 
@@ -34,6 +36,7 @@
 namespace V2_2 = android::hardware::graphics::composer::V2_2;
 namespace V2_3 = android::hardware::graphics::composer::V2_3;
 namespace V2_4 = android::hardware::graphics::composer::V2_4;
+namespace V3_0 = ::aidl::android::hardware::graphics::composer3;
 
 using types::V1_0::ColorTransform;
 using types::V1_0::Transform;
@@ -70,6 +73,8 @@
 using Vsync = IComposerClient::Vsync;
 using VsyncPeriodChangeConstraints = IComposerClient::VsyncPeriodChangeConstraints;
 using Hdr = aidl::android::hardware::graphics::common::Hdr;
+using DisplayConfiguration = V3_0::DisplayConfiguration;
+using VrrConfig = V3_0::VrrConfig;
 
 } // namespace hardware::graphics::composer::hal
 
@@ -145,6 +150,34 @@
     }
 }
 
+inline std::string to_string(
+        const std::optional<aidl::android::hardware::graphics::composer3::VrrConfig>& vrrConfig) {
+    if (vrrConfig) {
+        std::ostringstream out;
+        out << "{minFrameIntervalNs=" << vrrConfig->minFrameIntervalNs << ", ";
+        out << "frameIntervalPowerHints={";
+        if (vrrConfig->frameIntervalPowerHints) {
+            const auto& powerHint = *vrrConfig->frameIntervalPowerHints;
+            for (size_t i = 0; i < powerHint.size(); i++) {
+                if (i > 0) out << ", ";
+                out << "[frameIntervalNs=" << powerHint[i]->frameIntervalNs
+                    << ", averageRefreshPeriodNs=" << powerHint[i]->averageRefreshPeriodNs << "]";
+            }
+        }
+        out << "}, ";
+        out << "notifyExpectedPresentConfig={";
+        if (vrrConfig->notifyExpectedPresentConfig) {
+            out << "notifyExpectedPresentHeadsUpNs="
+                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs
+                << ", notifyExpectedPresentTimeoutNs="
+                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs;
+        }
+        out << "}}";
+        return out.str();
+    }
+    return "N/A";
+}
+
 inline std::string to_string(hardware::graphics::composer::hal::V2_4::Error error) {
     // 5 is reserved for historical reason, during validation 5 means has changes.
     if (ERROR_HAS_CHANGES == static_cast<int32_t>(error)) {
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 9b41da5..70d48de 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -269,6 +269,11 @@
     }
 }
 
+bool HidlComposer::getDisplayConfigurationsSupported() const {
+    // getDisplayConfigurations is not supported on the HIDL composer.
+    return false;
+};
+
 std::vector<Capability> HidlComposer::getCapabilities() {
     std::vector<Capability> capabilities;
     mComposer->getCapabilities([&](const auto& tmpCapabilities) {
@@ -477,6 +482,12 @@
     return error;
 }
 
+Error HidlComposer::getDisplayConfigurations(Display, int32_t /*maxFrameIntervalNs*/,
+                                             std::vector<DisplayConfiguration>*) {
+    LOG_ALWAYS_FATAL("getDisplayConfigurations should not have been called on this, as "
+                     "it's a HWC3 interface version 3 feature");
+}
+
 Error HidlComposer::getDisplayName(Display display, std::string* outName) {
     Error error = kDefaultError;
     mClient->getDisplayName(display, [&](const auto& tmpError, const auto& tmpName) {
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 0521acf..26d2222 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -167,6 +167,7 @@
     ~HidlComposer() override;
 
     bool isSupported(OptionalFeature) const;
+    bool getDisplayConfigurationsSupported() const;
 
     std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
             override;
@@ -196,6 +197,8 @@
     Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
                               int32_t* outValue) override;
     Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
+    Error getDisplayConfigurations(Display, int32_t maxFrameIntervalNs,
+                                   std::vector<DisplayConfiguration>*);
     Error getDisplayName(Display display, std::string* outName) override;
 
     Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 9c7576e..f00ef67 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -31,9 +31,9 @@
 #include <utils/Mutex.h>
 #include <utils/Trace.h>
 
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/WorkDuration.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/WorkDuration.h>
 
 #include <binder/IServiceManager.h>
 
@@ -49,11 +49,11 @@
 
 namespace impl {
 
-using android::hardware::power::Boost;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
-using android::hardware::power::SessionHint;
-using android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::WorkDuration;
 
 PowerAdvisor::~PowerAdvisor() = default;
 
@@ -230,7 +230,7 @@
             auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
             if (!ret.isOk()) {
                 ALOGW("Failed to set power hint target work duration with error: %s",
-                      ret.exceptionMessage().c_str());
+                      ret.getDescription().c_str());
                 mHintSessionRunning = false;
             }
         }
@@ -274,7 +274,7 @@
         auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
         if (!ret.isOk()) {
             ALOGW("Failed to report actual work durations with error: %s",
-                  ret.exceptionMessage().c_str());
+                  ret.getDescription().c_str());
             mHintSessionRunning = false;
             return;
         }
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index cfaa135..05e4c8b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -25,7 +25,7 @@
 #include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 
-#include <android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPower.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <powermanager/PowerHalController.h>
 #include <scheduler/Time.h>
@@ -247,13 +247,14 @@
     bool mHintSessionRunning = false;
 
     std::mutex mHintSessionMutex;
-    sp<hardware::power::IPowerHintSession> mHintSession GUARDED_BY(mHintSessionMutex) = nullptr;
+    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mHintSession
+            GUARDED_BY(mHintSessionMutex) = nullptr;
 
     // Initialize to true so we try to call, to check if it's supported
     bool mHasExpensiveRendering = true;
     bool mHasDisplayUpdateImminent = true;
     // Queue of actual durations saved to report
-    std::vector<hardware::power::WorkDuration> mHintSessionQueue;
+    std::vector<aidl::android::hardware::power::WorkDuration> mHintSessionQueue;
     // The latest values we have received for target and actual
     Duration mTargetDuration = kDefaultTargetDuration;
     std::optional<Duration> mActualDuration;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index ded734e..1b1307b 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -140,6 +140,10 @@
         janks.emplace_back("SurfaceFlinger Stuffing");
         jankType &= ~JankType::SurfaceFlingerStuffing;
     }
+    if (jankType & JankType::Dropped) {
+        janks.emplace_back("Dropped Frame");
+        jankType &= ~JankType::Dropped;
+    }
 
     // jankType should be 0 if all types of jank were checked for.
     LOG_ALWAYS_FATAL_IF(jankType != 0, "Unrecognized jank type value 0x%x", jankType);
@@ -264,6 +268,11 @@
         protoJank |= FrameTimelineEvent::JANK_SF_STUFFING;
         jankType &= ~JankType::SurfaceFlingerStuffing;
     }
+    if (jankType & JankType::Dropped) {
+        // Jank dropped does not append to other janks, it fully overrides.
+        protoJank |= FrameTimelineEvent::JANK_DROPPED;
+        jankType &= ~JankType::Dropped;
+    }
 
     // jankType should be 0 if all types of jank were checked for.
     LOG_ALWAYS_FATAL_IF(jankType != 0, "Unrecognized jank type value 0x%x", jankType);
@@ -357,6 +366,11 @@
     mRenderRate = renderRate;
 }
 
+Fps SurfaceFrame::getRenderRate() const {
+    std::lock_guard<std::mutex> lock(mMutex);
+    return mRenderRate ? *mRenderRate : mDisplayFrameRenderRate;
+}
+
 void SurfaceFrame::setGpuComposition() {
     std::scoped_lock lock(mMutex);
     mGpuComposition = true;
@@ -365,8 +379,7 @@
 std::optional<int32_t> SurfaceFrame::getJankType() const {
     std::scoped_lock lock(mMutex);
     if (mPresentState == PresentState::Dropped) {
-        // Return no jank if it's a dropped frame since we cannot attribute a jank to a it.
-        return JankType::None;
+        return JankType::Dropped;
     }
     if (mActuals.presentTime == 0) {
         // Frame hasn't been presented yet.
@@ -503,7 +516,8 @@
         // We classify prediction expired as AppDeadlineMissed as the
         // TokenManager::kMaxTokens we store is large enough to account for a
         // reasonable app, so prediction expire would mean a huge scheduling delay.
-        mJankType = JankType::AppDeadlineMissed;
+        mJankType = mPresentState != PresentState::Presented ? JankType::Dropped
+                                                             : JankType::AppDeadlineMissed;
         deadlineDelta = -1;
         return;
     }
@@ -594,17 +608,19 @@
             mJankType |= displayFrameJankType;
         }
     }
+    if (mPresentState != PresentState::Presented) {
+        mJankType = JankType::Dropped;
+        // Since frame was not presented, lets drop any present value
+        mActuals.presentTime = 0;
+    }
 }
 
 void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
-                             nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta) {
+                             Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
+                             nsecs_t displayPresentDelta) {
     std::scoped_lock lock(mMutex);
 
-    if (mPresentState != PresentState::Presented) {
-        // No need to update dropped buffers
-        return;
-    }
-
+    mDisplayFrameRenderRate = displayFrameRenderRate;
     mActuals.presentTime = presentTime;
     nsecs_t deadlineDelta = 0;
 
@@ -828,10 +844,11 @@
     mCurrentDisplayFrame->addSurfaceFrame(surfaceFrame);
 }
 
-void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate) {
+void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate,
+                                Fps renderRate) {
     ATRACE_CALL();
     std::scoped_lock lock(mMutex);
-    mCurrentDisplayFrame->onSfWakeUp(token, refreshRate,
+    mCurrentDisplayFrame->onSfWakeUp(token, refreshRate, renderRate,
                                      mTokenManager.getPredictionsForToken(token), wakeUpTime);
 }
 
@@ -851,11 +868,12 @@
     mSurfaceFrames.push_back(surfaceFrame);
 }
 
-void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, Fps refreshRate,
+void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                                              std::optional<TimelineItem> predictions,
                                              nsecs_t wakeUpTime) {
     mToken = token;
     mRefreshRate = refreshRate;
+    mRenderRate = renderRate;
     if (!predictions) {
         mPredictionState = PredictionState::Expired;
     } else {
@@ -1017,7 +1035,8 @@
     classifyJank(deadlineDelta, deltaToVsync, previousPresentTime);
 
     for (auto& surfaceFrame : mSurfaceFrames) {
-        surfaceFrame->onPresent(signalTime, mJankType, mRefreshRate, deadlineDelta, deltaToVsync);
+        surfaceFrame->onPresent(signalTime, mJankType, mRefreshRate, mRenderRate, deadlineDelta,
+                                deltaToVsync);
     }
 }
 
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index d54d22d..538ea12 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -183,6 +183,8 @@
     void setDropTime(nsecs_t dropTime);
     void setPresentState(PresentState presentState, nsecs_t lastLatchTime = 0);
     void setRenderRate(Fps renderRate);
+    // Return the render rate if it exists, otherwise returns the DisplayFrame's render rate.
+    Fps getRenderRate() const;
     void setGpuComposition();
 
     // When a bufferless SurfaceFrame is promoted to a buffer SurfaceFrame, we also have to update
@@ -197,7 +199,8 @@
     // displayRefreshRate, displayDeadlineDelta, and displayPresentDelta are propagated from the
     // display frame.
     void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
-                   nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta);
+                   Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
+                   nsecs_t displayPresentDelta);
     // All the timestamps are dumped relative to the baseTime
     void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const;
     // Dumps only the layer, token, is buffer, jank metadata, prediction and present states.
@@ -251,6 +254,8 @@
     int32_t mJankType GUARDED_BY(mMutex) = JankType::None;
     // Indicates if this frame was composited by the GPU or not
     bool mGpuComposition GUARDED_BY(mMutex) = false;
+    // Refresh rate for this frame.
+    Fps mDisplayFrameRenderRate GUARDED_BY(mMutex);
     // Rendering rate for this frame.
     std::optional<Fps> mRenderRate GUARDED_BY(mMutex);
     // Enum for the type of present
@@ -298,7 +303,8 @@
 
     // The first function called by SF for the current DisplayFrame. Fetches SF predictions based on
     // the token and sets the actualSfWakeTime for the current DisplayFrame.
-    virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) = 0;
+    virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate,
+                             Fps renderRate) = 0;
 
     // Sets the sfPresentTime and finalizes the current DisplayFrame. Tracks the
     // given present fence until it's signaled, and updates the present timestamps of all presented
@@ -374,8 +380,8 @@
         // and SYSTEM_TIME_MONOTONIC.
         void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
-        void onSfWakeUp(int64_t token, Fps refreshRate, std::optional<TimelineItem> predictions,
-                        nsecs_t wakeUpTime);
+        void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
+                        std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
         // Sets the appropriate metadata and classifies the jank.
         void onPresent(nsecs_t signalTime, nsecs_t previousPresentTime);
         // Adds the provided SurfaceFrame to the current display frame.
@@ -437,6 +443,8 @@
         // The refresh rate (vsync period) in nanoseconds as seen by SF during this DisplayFrame's
         // timeline
         Fps mRefreshRate;
+        // The current render rate for this DisplayFrame.
+        Fps mRenderRate;
         // TraceCookieCounter is used to obtain the cookie for sendig trace packets to perfetto.
         // Using a reference here because the counter is owned by FrameTimeline, which outlives
         // DisplayFrame.
@@ -453,7 +461,7 @@
             int32_t layerId, std::string layerName, std::string debugName, bool isBuffer,
             GameMode) override;
     void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame) override;
-    void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) override;
+    void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                       const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
index c26edb5..0788d1a 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -54,7 +54,6 @@
     gui::LayerMetadata metadata;
     pid_t ownerPid;
     uid_t ownerUid;
-    uint32_t textureName;
     uint32_t sequence;
     bool addToRoot = true;
     wp<IBinder> parentHandle = nullptr;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 163d345..1e5a6fb 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -60,9 +60,9 @@
             return;
         }
     }
-    if (traversalPath.hasRelZLoop()) {
-        LOG_ALWAYS_FATAL("Found relative z loop layerId:%d", traversalPath.invalidRelativeRootId);
-    }
+
+    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(traversalPath.hasRelZLoop(), "Found relative z loop layerId:%d",
+                                    traversalPath.invalidRelativeRootId);
     for (auto& [child, childVariant] : mChildren) {
         ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id,
                                                          childVariant);
@@ -104,9 +104,7 @@
                            [child](const std::pair<LayerHierarchy*, Variant>& x) {
                                return x.first == child;
                            });
-    if (it == mChildren.end()) {
-        LOG_ALWAYS_FATAL("Could not find child!");
-    }
+    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mChildren.end(), "Could not find child!");
     mChildren.erase(it);
 }
 
@@ -119,11 +117,8 @@
                            [hierarchy](std::pair<LayerHierarchy*, Variant>& child) {
                                return child.first == hierarchy;
                            });
-    if (it == mChildren.end()) {
-        LOG_ALWAYS_FATAL("Could not find child!");
-    } else {
-        it->second = variant;
-    }
+    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mChildren.end(), "Could not find child!");
+    it->second = variant;
 }
 
 const RequestedLayerState* LayerHierarchy::getLayer() const {
@@ -149,13 +144,37 @@
     return debug + "}";
 }
 
-std::string LayerHierarchy::getDebugString(const char* prefix) const {
-    std::string debug = prefix + getDebugStringShort();
-    for (auto& [child, childVariant] : mChildren) {
-        std::string childPrefix = "  " + std::string(prefix) + " " + std::to_string(childVariant);
-        debug += "\n" + child->getDebugString(childPrefix.c_str());
+void LayerHierarchy::dump(std::ostream& out, const std::string& prefix,
+                          LayerHierarchy::Variant variant, bool isLastChild,
+                          bool includeMirroredHierarchy) const {
+    if (!mLayer) {
+        out << " ROOT";
+    } else {
+        out << prefix + (isLastChild ? "└─ " : "├─ ");
+        if (variant == LayerHierarchy::Variant::Relative) {
+            out << "(Relative) ";
+        } else if (variant == LayerHierarchy::Variant::Mirror) {
+            if (!includeMirroredHierarchy) {
+                out << "(Mirroring) " << *mLayer << "\n" + prefix + "   └─ ...";
+                return;
+            }
+            out << "(Mirroring) ";
+        }
+        out << *mLayer;
     }
-    return debug;
+
+    for (size_t i = 0; i < mChildren.size(); i++) {
+        auto& [child, childVariant] = mChildren[i];
+        if (childVariant == LayerHierarchy::Variant::Detached) continue;
+        const bool lastChild = i == (mChildren.size() - 1);
+        std::string childPrefix = prefix;
+        if (mLayer) {
+            childPrefix += (isLastChild ? "   " : "│  ");
+        }
+        out << "\n";
+        child->dump(out, childPrefix, childVariant, lastChild, includeMirroredHierarchy);
+    }
+    return;
 }
 
 bool LayerHierarchy::hasRelZLoop(uint32_t& outInvalidRelativeRoot) const {
@@ -402,9 +421,8 @@
 LayerHierarchy* LayerHierarchyBuilder::getHierarchyFromId(uint32_t layerId, bool crashOnFailure) {
     auto it = mLayerIdToHierarchy.find(layerId);
     if (it == mLayerIdToHierarchy.end()) {
-        if (crashOnFailure) {
-            LOG_ALWAYS_FATAL("Could not find hierarchy for layer id %d", layerId);
-        }
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(crashOnFailure, "Could not find hierarchy for layer id %d",
+                                        layerId);
         return nullptr;
     };
 
@@ -421,8 +439,11 @@
     std::stringstream ss;
     ss << "TraversalPath{.id = " << id;
 
-    if (mirrorRootId != UNASSIGNED_LAYER_ID) {
-        ss << ", .mirrorRootId=" << mirrorRootId;
+    if (!mirrorRootIds.empty()) {
+        ss << ", .mirrorRootIds=";
+        for (auto rootId : mirrorRootIds) {
+            ss << rootId << ",";
+        }
     }
 
     if (!relativeRootIds.empty()) {
@@ -439,13 +460,6 @@
     return ss.str();
 }
 
-LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::getMirrorRoot() const {
-    LOG_ALWAYS_FATAL_IF(!isClone(), "Cannot get mirror root of a non cloned node");
-    TraversalPath mirrorRootPath = *this;
-    mirrorRootPath.id = mirrorRootId;
-    return mirrorRootPath;
-}
-
 // Helper class to update a passed in TraversalPath when visiting a child. When the object goes out
 // of scope the TraversalPath is reset to its original state.
 LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath,
@@ -457,7 +471,7 @@
     traversalPath.id = layerId;
     traversalPath.variant = variant;
     if (variant == LayerHierarchy::Variant::Mirror) {
-        traversalPath.mirrorRootId = mParentPath.id;
+        traversalPath.mirrorRootIds.emplace_back(mParentPath.id);
     } else if (variant == LayerHierarchy::Variant::Relative) {
         if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
                       layerId) != traversalPath.relativeRootIds.end()) {
@@ -472,7 +486,7 @@
     // Reset the traversal id to its original parent state using the state that was saved in
     // the constructor.
     if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
-        mTraversalPath.mirrorRootId = mParentPath.mirrorRootId;
+        mTraversalPath.mirrorRootIds.pop_back();
     } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
         mTraversalPath.relativeRootIds.pop_back();
     }
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index 5389ada..ba2e262 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -57,25 +57,25 @@
     // ├─ B {Traversal path id = 2}
     // │  ├─ C {Traversal path id = 3}
     // │  ├─ D {Traversal path id = 4}
-    // │  └─ E {Traversal path id = 5}
-    // ├─ F (Mirrors B) {Traversal path id = 6}
-    // └─ G (Mirrors F) {Traversal path id = 7}
+    // │  └─ E (Mirrors C) {Traversal path id = 5}
+    // └─ F (Mirrors B) {Traversal path id = 6}
     //
-    // C, D and E can be traversed via B or via F then B or via G then F then B.
+    // C can be traversed via B or E or F and or via F then E.
     // Depending on how the node is reached, its properties such as geometry or visibility might be
     // different. And we can uniquely identify the node by keeping track of the nodes leading up to
     // it. But to be more efficient we only need to track the nodes id and the top mirror root path.
     // So C for example, would have the following unique traversal paths:
     //  - {Traversal path id = 3}
-    //  - {Traversal path id = 3, mirrorRootId = 6}
-    //  - {Traversal path id = 3, mirrorRootId = 7}
+    //  - {Traversal path id = 3, mirrorRootIds = 5}
+    //  - {Traversal path id = 3, mirrorRootIds = 6}
+    //  - {Traversal path id = 3, mirrorRootIds = 6, 5}
 
     struct TraversalPath {
         uint32_t id;
         LayerHierarchy::Variant variant;
         // Mirrored layers can have a different geometry than their parents so we need to track
         // the mirror roots in the traversal.
-        uint32_t mirrorRootId = UNASSIGNED_LAYER_ID;
+        ftl::SmallVector<uint32_t, 5> mirrorRootIds;
         // Relative layers can be visited twice, once by their parent and then once again by
         // their relative parent. We keep track of the roots here to detect any loops in the
         // hierarchy. If a relative root already exists in the list while building the
@@ -93,11 +93,10 @@
         // Returns true if the node or its parents are not Detached.
         bool isAttached() const { return !detached; }
         // Returns true if the node is a clone.
-        bool isClone() const { return mirrorRootId != UNASSIGNED_LAYER_ID; }
-        TraversalPath getMirrorRoot() const;
+        bool isClone() const { return !mirrorRootIds.empty(); }
 
         bool operator==(const TraversalPath& other) const {
-            return id == other.id && mirrorRootId == other.mirrorRootId;
+            return id == other.id && mirrorRootIds == other.mirrorRootIds;
         }
         std::string toString() const;
 
@@ -107,8 +106,8 @@
     struct TraversalPathHash {
         std::size_t operator()(const LayerHierarchy::TraversalPath& key) const {
             uint32_t hashCode = key.id * 31;
-            if (key.mirrorRootId != UNASSIGNED_LAYER_ID) {
-                hashCode += key.mirrorRootId * 31;
+            for (uint32_t mirrorRootId : key.mirrorRootIds) {
+                hashCode += mirrorRootId * 31;
             }
             return std::hash<size_t>{}(hashCode);
         }
@@ -156,7 +155,20 @@
     const RequestedLayerState* getLayer() const;
     const LayerHierarchy* getRelativeParent() const;
     const LayerHierarchy* getParent() const;
-    std::string getDebugString(const char* prefix = "") const;
+    friend std::ostream& operator<<(std::ostream& os, const LayerHierarchy& obj) {
+        std::string prefix = " ";
+        obj.dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false,
+                 /*includeMirroredHierarchy*/ false);
+        return os;
+    }
+    std::string dump() const {
+        std::string prefix = " ";
+        std::ostringstream os;
+        dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false,
+             /*includeMirroredHierarchy*/ true);
+        return os.str();
+    }
+
     std::string getDebugStringShort() const;
     // Traverse the hierarchy and return true if loops are found. The outInvalidRelativeRoot
     // will contain the first relative root that was visited twice in a traversal.
@@ -172,6 +184,8 @@
     void updateChild(LayerHierarchy*, LayerHierarchy::Variant);
     void traverseInZOrder(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
     void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
+    void dump(std::ostream& out, const std::string& prefix, LayerHierarchy::Variant variant,
+              bool isLastChild, bool includeMirroredHierarchy) const;
 
     const RequestedLayerState* mLayer;
     LayerHierarchy* mParent = nullptr;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 1712137..a826ec1 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -45,11 +45,11 @@
     for (auto& newLayer : newLayers) {
         RequestedLayerState& layer = *newLayer.get();
         auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer});
-        if (!inserted) {
-            LOG_ALWAYS_FATAL("Duplicate layer id found. New layer: %s Existing layer: %s",
-                             layer.getDebugString().c_str(),
-                             it->second.owner.getDebugString().c_str());
-        }
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!inserted,
+                                        "Duplicate layer id found. New layer: %s Existing layer: "
+                                        "%s",
+                                        layer.getDebugString().c_str(),
+                                        it->second.owner.getDebugString().c_str());
         mAddedLayers.push_back(newLayer.get());
         mChangedLayers.push_back(newLayer.get());
         layer.parentId = linkLayer(layer.parentId, layer.id);
@@ -85,14 +85,15 @@
     }
 }
 
-void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles,
-                                               bool ignoreUnknownHandles) {
+void LayerLifecycleManager::onHandlesDestroyed(
+        const std::vector<std::pair<uint32_t, std::string /* debugName */>>& destroyedHandles,
+        bool ignoreUnknownHandles) {
     std::vector<uint32_t> layersToBeDestroyed;
-    for (const auto& layerId : destroyedHandles) {
+    for (const auto& [layerId, name] : destroyedHandles) {
         auto it = mIdToLayer.find(layerId);
         if (it == mIdToLayer.end()) {
-            LOG_ALWAYS_FATAL_IF(!ignoreUnknownHandles, "%s Layerid not found %d", __func__,
-                                layerId);
+            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!ignoreUnknownHandles, "%s Layerid not found %s[%d]",
+                                            __func__, name.c_str(), layerId);
             continue;
         }
         RequestedLayerState& layer = it->second.owner;
@@ -113,10 +114,8 @@
     for (size_t i = 0; i < layersToBeDestroyed.size(); i++) {
         uint32_t layerId = layersToBeDestroyed[i];
         auto it = mIdToLayer.find(layerId);
-        if (it == mIdToLayer.end()) {
-            LOG_ALWAYS_FATAL("%s Layer with id %d not found", __func__, layerId);
-            continue;
-        }
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mIdToLayer.end(), "%s Layer with id %d not found",
+                                        __func__, layerId);
 
         RequestedLayerState& layer = it->second.owner;
 
@@ -135,11 +134,9 @@
         auto& references = it->second.references;
         for (uint32_t linkedLayerId : references) {
             RequestedLayerState* linkedLayer = getLayerFromId(linkedLayerId);
-            if (!linkedLayer) {
-                LOG_ALWAYS_FATAL("%s Layerid reference %d not found for %d", __func__,
-                                 linkedLayerId, layer.id);
-                continue;
-            };
+            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!linkedLayer,
+                                            "%s Layerid reference %d not found for %d", __func__,
+                                            linkedLayerId, layer.id);
             if (linkedLayer->parentId == layer.id) {
                 linkedLayer->parentId = UNASSIGNED_LAYER_ID;
                 if (linkedLayer->canBeDestroyed()) {
@@ -191,17 +188,17 @@
 
             RequestedLayerState* layer = getLayerFromId(layerId);
             if (layer == nullptr) {
-                LOG_ALWAYS_FATAL_IF(!ignoreUnknownLayers, "%s Layer with layerid=%d not found",
-                                    __func__, layerId);
+                LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!ignoreUnknownLayers,
+                                                "%s Layer with layerid=%d not found", __func__,
+                                                layerId);
                 continue;
             }
 
-            if (!layer->handleAlive) {
-                LOG_ALWAYS_FATAL("%s Layer's with layerid=%d) is not alive. Possible out of "
-                                 "order LayerLifecycleManager updates",
-                                 __func__, layerId);
-                continue;
-            }
+            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!layer->handleAlive,
+                                            "%s Layer's with layerid=%d) is not alive. Possible "
+                                            "out of "
+                                            "order LayerLifecycleManager updates",
+                                            __func__, layerId);
 
             if (layer->changes.get() == 0) {
                 mChangedLayers.push_back(layer);
@@ -241,7 +238,7 @@
                     RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
                     layer->bgColorLayerId = UNASSIGNED_LAYER_ID;
                     bgColorLayer->parentId = unlinkLayer(bgColorLayer->parentId, bgColorLayer->id);
-                    onHandlesDestroyed({bgColorLayer->id});
+                    onHandlesDestroyed({{bgColorLayer->id, bgColorLayer->debugName}});
                 } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) {
                     RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
                     bgColorLayer->color = layer->bgColor;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 48571bf..9aff78e 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -47,7 +47,8 @@
     // Ignore unknown handles when iteroping with legacy front end. In the old world, we
     // would create child layers which are not necessary with the new front end. This means
     // we will get notified for handle changes that don't exist in the new front end.
-    void onHandlesDestroyed(const std::vector<uint32_t>&, bool ignoreUnknownHandles = false);
+    void onHandlesDestroyed(const std::vector<std::pair<uint32_t, std::string /* debugName */>>&,
+                            bool ignoreUnknownHandles = false);
 
     // Detaches the layer from its relative parent to prevent a loop in the
     // layer hierarchy. This overrides the RequestedLayerState and leaves
diff --git a/services/surfaceflinger/FrontEnd/LayerLog.h b/services/surfaceflinger/FrontEnd/LayerLog.h
index 4943483..3845dfe 100644
--- a/services/surfaceflinger/FrontEnd/LayerLog.h
+++ b/services/surfaceflinger/FrontEnd/LayerLog.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "Tracing/TransactionTracing.h"
+
 // Uncomment to trace layer updates for a single layer
 // #define LOG_LAYER 1
 
@@ -27,3 +29,17 @@
 #endif
 
 #define LLOGD(LAYER_ID, x, ...) ALOGD("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__);
+
+#define LLOG_ALWAYS_FATAL_WITH_TRACE(...)                                               \
+    do {                                                                                \
+        TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false); \
+        LOG_ALWAYS_FATAL(##__VA_ARGS__);                                                \
+    } while (false)
+
+#define LLOG_ALWAYS_FATAL_WITH_TRACE_IF(cond, ...)                                          \
+    do {                                                                                    \
+        if (__predict_false(cond)) {                                                        \
+            TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false); \
+        }                                                                                   \
+        LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__);                                           \
+    } while (false)
\ No newline at end of file
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index f0826c6..7a85da0 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -19,6 +19,7 @@
 #define LOG_TAG "SurfaceFlinger"
 
 #include "LayerSnapshot.h"
+#include "Layer.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -39,6 +40,63 @@
     }
 }
 
+std::ostream& operator<<(std::ostream& os, const ui::Transform& transform) {
+    const uint32_t type = transform.getType();
+    const uint32_t orientation = transform.getOrientation();
+    if (type == ui::Transform::IDENTITY) {
+        return os;
+    }
+
+    if (type & ui::Transform::UNKNOWN) {
+        std::string out;
+        transform.dump(out, "", "");
+        os << out;
+        return os;
+    }
+
+    if (type & ui::Transform::ROTATE) {
+        switch (orientation) {
+            case ui::Transform::ROT_0:
+                os << "ROT_0";
+                break;
+            case ui::Transform::FLIP_H:
+                os << "FLIP_H";
+                break;
+            case ui::Transform::FLIP_V:
+                os << "FLIP_V";
+                break;
+            case ui::Transform::ROT_90:
+                os << "ROT_90";
+                break;
+            case ui::Transform::ROT_180:
+                os << "ROT_180";
+                break;
+            case ui::Transform::ROT_270:
+                os << "ROT_270";
+                break;
+            case ui::Transform::ROT_INVALID:
+            default:
+                os << "ROT_INVALID";
+                break;
+        }
+    }
+
+    if (type & ui::Transform::SCALE) {
+        std::string out;
+        android::base::StringAppendF(&out, " scale x=%.4f y=%.4f ", transform.getScaleX(),
+                                     transform.getScaleY());
+        os << out;
+    }
+
+    if (type & ui::Transform::TRANSLATE) {
+        std::string out;
+        android::base::StringAppendF(&out, " tx=%.4f ty=%.4f ", transform.tx(), transform.ty());
+        os << out;
+    }
+
+    return os;
+}
+
 } // namespace
 
 LayerSnapshot::LayerSnapshot(const RequestedLayerState& state,
@@ -59,7 +117,7 @@
     }
     sequence = static_cast<int32_t>(state.id);
     name = state.name;
-    textureName = state.textureName;
+    debugName = state.debugName;
     premultipliedAlpha = state.premultipliedAlpha;
     inputInfo.name = state.name;
     inputInfo.id = static_cast<int32_t>(uniqueSequence);
@@ -73,6 +131,8 @@
             ? path
             : LayerHierarchy::TraversalPath::ROOT;
     reachablilty = LayerSnapshot::Reachablilty::Unreachable;
+    frameRateSelectionPriority = state.frameRateSelectionPriority;
+    layerMetadata = state.metadata;
 }
 
 // As documented in libhardware header, formats in the range
@@ -180,13 +240,13 @@
     if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) return "eLayerSkipScreenshot";
     if (invalidTransform) return "invalidTransform";
     if (color.a == 0.0f && !hasBlur()) return "alpha = 0 and no blur";
-    if (!hasSomethingToDraw()) return "!hasSomethingToDraw";
+    if (!hasSomethingToDraw()) return "nothing to draw";
 
     // visible
     std::stringstream reason;
     if (sidebandStream != nullptr) reason << " sidebandStream";
     if (externalTexture != nullptr)
-        reason << " buffer:" << externalTexture->getId() << " frame:" << frameNumber;
+        reason << " buffer=" << externalTexture->getId() << " frame=" << frameNumber;
     if (fillsColor() || color.a > 0.0f) reason << " color{" << color << "}";
     if (drawShadows()) reason << " shadowSettings.length=" << shadowSettings.length;
     if (backgroundBlurRadius > 0) reason << " backgroundBlurRadius=" << backgroundBlurRadius;
@@ -232,6 +292,39 @@
     return debug.str();
 }
 
+std::ostream& operator<<(std::ostream& out, const LayerSnapshot& obj) {
+    out << "Layer [" << obj.path.id;
+    if (!obj.path.mirrorRootIds.empty()) {
+        out << " mirrored from ";
+        for (auto rootId : obj.path.mirrorRootIds) {
+            out << rootId << ",";
+        }
+    }
+    out << "] " << obj.name << "\n    " << (obj.isVisible ? "visible" : "invisible")
+        << " reason=" << obj.getIsVisibleReason();
+
+    if (!obj.geomLayerBounds.isEmpty()) {
+        out << "\n    bounds={" << obj.transformedBounds.left << "," << obj.transformedBounds.top
+            << "," << obj.transformedBounds.bottom << "," << obj.transformedBounds.right << "}";
+    }
+
+    if (obj.geomLayerTransform.getType() != ui::Transform::IDENTITY) {
+        out << " toDisplayTransform={" << obj.geomLayerTransform << "}";
+    }
+
+    if (obj.hasInputInfo()) {
+        out << "\n    input{"
+            << "(" << obj.inputInfo.inputConfig.string() << ")";
+        if (obj.touchCropId != UNASSIGNED_LAYER_ID) out << " touchCropId=" << obj.touchCropId;
+        if (obj.inputInfo.replaceTouchableRegionWithCrop) out << " replaceTouchableRegionWithCrop";
+        auto touchableRegion = obj.inputInfo.touchableRegion.getBounds();
+        out << " touchableRegion={" << touchableRegion.left << "," << touchableRegion.top << ","
+            << touchableRegion.bottom << "," << touchableRegion.right << "}"
+            << "}";
+    }
+    return out;
+}
+
 FloatRect LayerSnapshot::sourceBounds() const {
     if (!externalTexture) {
         return geomLayerBounds;
@@ -255,10 +348,9 @@
     clientChanges = requested.what;
     changes = requested.changes;
     contentDirty = requested.what & layer_state_t::CONTENT_DIRTY;
-    // TODO(b/238781169) scope down the changes to only buffer updates.
-    hasReadyFrame = requested.hasReadyFrame();
+    hasReadyFrame = requested.autoRefresh;
     sidebandStreamHasFrame = requested.hasSidebandStreamFrame();
-    updateSurfaceDamage(requested, hasReadyFrame, forceFullDamage, surfaceDamage);
+    updateSurfaceDamage(requested, requested.hasReadyFrame(), forceFullDamage, surfaceDamage);
 
     if (forceUpdate || requested.what & layer_state_t::eTransparentRegionChanged) {
         transparentRegionHint = requested.transparentRegion;
@@ -274,7 +366,7 @@
         geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse;
     }
     if (forceUpdate || requested.what & layer_state_t::eDataspaceChanged) {
-        dataspace = requested.dataspace;
+        dataspace = Layer::translateDataspace(requested.dataspace);
     }
     if (forceUpdate || requested.what & layer_state_t::eExtendedRangeBrightnessChanged) {
         currentHdrSdrRatio = requested.currentHdrSdrRatio;
@@ -290,7 +382,6 @@
         sidebandStream = requested.sidebandStream;
     }
     if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) {
-        shadowRadius = requested.shadowRadius;
         shadowSettings.length = requested.shadowRadius;
     }
     if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) {
@@ -306,6 +397,15 @@
         geomCrop = requested.crop;
     }
 
+    if (forceUpdate || requested.what & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
+        const auto compatibility =
+                Layer::FrameRate::convertCompatibility(requested.defaultFrameRateCompatibility);
+        if (defaultFrameRateCompatibility != compatibility) {
+            clientChanges |= layer_state_t::eDefaultFrameRateCompatibilityChanged;
+        }
+        defaultFrameRateCompatibility = compatibility;
+    }
+
     if (forceUpdate ||
         requested.what &
                 (layer_state_t::eFlagsChanged | layer_state_t::eBufferChanged |
@@ -379,19 +479,9 @@
     if (forceUpdate ||
         requested.what &
                 (layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
-                 layer_state_t::eApiChanged)) {
-        isHdrY410 = requested.dataspace == ui::Dataspace::BT2020_ITU_PQ &&
-                requested.api == NATIVE_WINDOW_API_MEDIA &&
-                requested.bufferData->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102;
-    }
-
-    if (forceUpdate ||
-        requested.what &
-                (layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
                  layer_state_t::eApiChanged | layer_state_t::eShadowRadiusChanged |
                  layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged)) {
-        forceClientComposition = isHdrY410 || shadowSettings.length > 0 ||
-                requested.blurRegions.size() > 0 || stretchEffect.hasEffect();
+        forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect();
     }
 
     if (forceUpdate ||
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 2f45d52..4fd6495 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -41,10 +41,6 @@
     }
 };
 
-struct ChildState {
-    bool hasValidFrameRate = false;
-};
-
 // LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition
 // Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
 // passed to Render Engine are created using properties stored on this struct.
@@ -67,22 +63,20 @@
     // generated from the same layer, for example when mirroring.
     int32_t sequence;
     std::string name;
-    uint32_t textureName;
+    std::string debugName;
     bool contentOpaque;
     bool layerOpaqueFlagSet;
     RoundedCornerState roundedCorner;
     FloatRect transformedBounds;
     Rect transformedBoundsWithoutTransparentRegion;
-    renderengine::ShadowSettings shadowSettings;
     bool premultipliedAlpha;
-    bool isHdrY410;
     ui::Transform parentTransform;
     Rect bufferSize;
     Rect croppedBufferSize;
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
     gui::LayerMetadata layerMetadata;
     gui::LayerMetadata relativeLayerMetadata;
-    bool hasReadyFrame;
+    bool hasReadyFrame; // used in post composition to check if there is another frame ready
     ui::Transform localTransformInverse;
     gui::WindowInfo inputInfo;
     ui::Transform localTransform;
@@ -90,15 +84,18 @@
     bool isTrustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
+    scheduler::LayerInfo::FrameRate inheritedFrameRate;
+    scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
+    scheduler::FrameRateCompatibility defaultFrameRateCompatibility =
+            scheduler::FrameRateCompatibility::Default;
     ui::Transform::RotationFlags fixedTransformHint;
     std::optional<ui::Transform::RotationFlags> transformHint;
     bool handleSkipScreenshotFlag = false;
-    int32_t frameRateSelectionPriority;
+    int32_t frameRateSelectionPriority = -1;
     LayerHierarchy::TraversalPath mirrorRootPath;
     uint32_t touchCropId;
     gui::Uid uid = gui::Uid::INVALID;
     gui::Pid pid = gui::Pid::INVALID;
-    ChildState childState;
     enum class Reachablilty : uint32_t {
         // Can traverse the hierarchy from a root node and reach this snapshot
         Reachable,
@@ -126,6 +123,8 @@
         ReachableByRelativeParent
     };
     Reachablilty reachablilty;
+    // True when the surfaceDamage is recognized as a small area update.
+    bool isSmallDirty = false;
 
     static bool isOpaqueFormat(PixelFormat format);
     static bool isTransformValid(const ui::Transform& t);
@@ -145,7 +144,7 @@
     bool hasInputInfo() const;
     FloatRect sourceBounds() const;
     Hwc2::IComposerClient::BlendMode getBlendMode(const RequestedLayerState& requested) const;
-
+    friend std::ostream& operator<<(std::ostream& os, const LayerSnapshot& obj);
     void merge(const RequestedLayerState& requested, bool forceUpdate, bool displayChanges,
                bool forceFullDamage, uint32_t displayRotationFlags);
 };
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 21f0a67..2a0857d 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -29,6 +29,7 @@
 
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/Hal.h"
+#include "Layer.h" // eFrameRateSelectionPriority constants
 #include "LayerLog.h"
 #include "LayerSnapshotBuilder.h"
 #include "TimeStats/TimeStats.h"
@@ -177,12 +178,7 @@
         info.touchableRegion.clear();
     }
 
-    const Rect roundedFrameInDisplay =
-            getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);
-    info.frameLeft = roundedFrameInDisplay.left;
-    info.frameTop = roundedFrameInDisplay.top;
-    info.frameRight = roundedFrameInDisplay.right;
-    info.frameBottom = roundedFrameInDisplay.bottom;
+    info.frame = getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);
 
     ui::Transform inputToLayer;
     inputToLayer.set(inputBounds.left, inputBounds.top);
@@ -357,7 +353,7 @@
     snapshot.isSecure = false;
     snapshot.color.a = 1.0_hf;
     snapshot.colorTransformIsIdentity = true;
-    snapshot.shadowRadius = 0.f;
+    snapshot.shadowSettings.length = 0.f;
     snapshot.layerMetadata.mMap.clear();
     snapshot.relativeLayerMetadata.mMap.clear();
     snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED;
@@ -522,12 +518,9 @@
         const Args& args, const LayerHierarchy& hierarchy,
         LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot,
         int depth) {
-    if (depth > 50) {
-        TransactionTraceWriter::getInstance().invoke("layer_builder_stack_overflow_",
-                                                     /*overwrite=*/false);
-        LOG_ALWAYS_FATAL("Cycle detected in LayerSnapshotBuilder. See "
-                         "builder_stack_overflow_transactions.winscope");
-    }
+    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(depth > 50,
+                                    "Cycle detected in LayerSnapshotBuilder. See "
+                                    "builder_stack_overflow_transactions.winscope");
 
     const RequestedLayerState* layer = hierarchy.getLayer();
     LayerSnapshot* snapshot = getSnapshot(traversalPath);
@@ -539,7 +532,7 @@
                         primaryDisplayRotationFlags);
         snapshot->changes |= RequestedLayerState::Changes::Created;
     }
-    scheduler::LayerInfo::FrameRate oldFrameRate = snapshot->frameRate;
+
     if (traversalPath.isRelative()) {
         bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative;
         updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args);
@@ -557,12 +550,9 @@
         const LayerSnapshot& childSnapshot =
                 updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot,
                                            depth + 1);
-        updateChildState(*snapshot, childSnapshot, args);
+        updateFrameRateFromChildSnapshot(*snapshot, childSnapshot, args);
     }
 
-    if (oldFrameRate == snapshot->frameRate) {
-        snapshot->changes.clear(RequestedLayerState::Changes::FrameRate);
-    }
     return *snapshot;
 }
 
@@ -665,36 +655,43 @@
     }
 }
 
-void LayerSnapshotBuilder::updateChildState(LayerSnapshot& snapshot,
-                                            const LayerSnapshot& childSnapshot, const Args& args) {
-    if (snapshot.childState.hasValidFrameRate) {
+void LayerSnapshotBuilder::updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
+                                                            const LayerSnapshot& childSnapshot,
+                                                            const Args& args) {
+    if (args.forceUpdate == ForceUpdateFlags::NONE &&
+        !args.layerLifecycleManager.getGlobalChanges().any(
+                RequestedLayerState::Changes::Hierarchy) &&
+        !childSnapshot.changes.any(RequestedLayerState::Changes::FrameRate) &&
+        !snapshot.changes.any(RequestedLayerState::Changes::FrameRate)) {
         return;
     }
-    if (args.forceUpdate == ForceUpdateFlags::ALL ||
-        childSnapshot.changes.test(RequestedLayerState::Changes::FrameRate)) {
-        // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes
-        // for the same reason we are allowing touch boost for those layers. See
-        // RefreshRateSelector::rankFrameRates for details.
-        using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
-        const auto layerVotedWithDefaultCompatibility = childSnapshot.frameRate.rate.isValid() &&
-                childSnapshot.frameRate.type == FrameRateCompatibility::Default;
-        const auto layerVotedWithNoVote =
-                childSnapshot.frameRate.type == FrameRateCompatibility::NoVote;
-        const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.rate.isValid() &&
-                childSnapshot.frameRate.type == FrameRateCompatibility::Exact;
 
-        snapshot.childState.hasValidFrameRate |= layerVotedWithDefaultCompatibility ||
-                layerVotedWithNoVote || layerVotedWithExactCompatibility;
+    using FrameRateCompatibility = scheduler::FrameRateCompatibility;
+    if (snapshot.frameRate.isValid()) {
+        // we already have a valid framerate.
+        return;
+    }
 
-        // If we don't have a valid frame rate, but the children do, we set this
-        // layer as NoVote to allow the children to control the refresh rate
-        if (!snapshot.frameRate.rate.isValid() &&
-            snapshot.frameRate.type != FrameRateCompatibility::NoVote &&
-            snapshot.childState.hasValidFrameRate) {
-            snapshot.frameRate =
-                    scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote);
-            snapshot.changes |= childSnapshot.changes & RequestedLayerState::Changes::FrameRate;
-        }
+    // We return whether this layer or its children has a vote. We ignore ExactOrMultiple votes
+    // for the same reason we are allowing touch boost for those layers. See
+    // RefreshRateSelector::rankFrameRates for details.
+    const auto layerVotedWithDefaultCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
+            childSnapshot.frameRate.vote.type == FrameRateCompatibility::Default;
+    const auto layerVotedWithNoVote =
+            childSnapshot.frameRate.vote.type == FrameRateCompatibility::NoVote;
+    const auto layerVotedWithCategory =
+            childSnapshot.frameRate.category != FrameRateCategory::Default;
+    const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
+            childSnapshot.frameRate.vote.type == FrameRateCompatibility::Exact;
+
+    bool childHasValidFrameRate = layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
+            layerVotedWithCategory || layerVotedWithExactCompatibility;
+
+    // If we don't have a valid frame rate, but the children do, we set this
+    // layer as NoVote to allow the children to control the refresh rate
+    if (childHasValidFrameRate) {
+        snapshot.frameRate = scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote);
+        snapshot.changes |= RequestedLayerState::Changes::FrameRate;
     }
 }
 
@@ -734,6 +731,10 @@
                 : parentSnapshot.outputFilter.layerStack;
     }
 
+    if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
+        snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+    }
+
     if (snapshot.isHiddenByPolicyFromParent &&
         !snapshot.changes.test(RequestedLayerState::Changes::Created)) {
         if (forceUpdate ||
@@ -764,10 +765,6 @@
                 (requested.flags & layer_state_t::eLayerSkipScreenshot);
     }
 
-    if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
-        snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
-    }
-
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eStretchChanged) {
         snapshot.stretchEffect = (requested.stretchEffect.hasEffect())
                 ? requested.stretchEffect
@@ -811,12 +808,38 @@
         }
     }
 
-    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::FrameRate)) {
-        snapshot.frameRate = (requested.requestedFrameRate.rate.isValid() ||
-                              (requested.requestedFrameRate.type ==
-                               scheduler::LayerInfo::FrameRateCompatibility::NoVote))
-                ? requested.requestedFrameRate
-                : parentSnapshot.frameRate;
+    if (forceUpdate ||
+        args.layerLifecycleManager.getGlobalChanges().any(
+                RequestedLayerState::Changes::Hierarchy) ||
+        snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
+                             RequestedLayerState::Changes::Hierarchy)) {
+        bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
+                scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+        if (!requested.requestedFrameRate.isValid() || shouldOverrideChildren) {
+            snapshot.inheritedFrameRate = parentSnapshot.inheritedFrameRate;
+        } else {
+            snapshot.inheritedFrameRate = requested.requestedFrameRate;
+        }
+        // Set the framerate as the inherited frame rate and allow children to override it if
+        // needed.
+        snapshot.frameRate = snapshot.inheritedFrameRate;
+        snapshot.changes |= RequestedLayerState::Changes::FrameRate;
+    }
+
+    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
+        const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                requested.frameRateSelectionStrategy);
+        snapshot.frameRateSelectionStrategy =
+                strategy == scheduler::LayerInfo::FrameRateSelectionStrategy::Self
+                ? parentSnapshot.frameRateSelectionStrategy
+                : strategy;
+    }
+
+    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
+        snapshot.frameRateSelectionPriority =
+                (requested.frameRateSelectionPriority == Layer::PRIORITY_UNSET)
+                ? parentSnapshot.frameRateSelectionPriority
+                : requested.frameRateSelectionPriority;
     }
 
     if (forceUpdate ||
@@ -838,8 +861,9 @@
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eCornerRadiusChanged ||
-        snapshot.changes.any(RequestedLayerState::Changes::Geometry)) {
-        updateRoundedCorner(snapshot, requested, parentSnapshot);
+        snapshot.changes.any(RequestedLayerState::Changes::Geometry |
+                             RequestedLayerState::Changes::BufferUsageFlags)) {
+        updateRoundedCorner(snapshot, requested, parentSnapshot, args);
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eShadowRadiusChanged ||
@@ -854,8 +878,8 @@
     }
 
     // computed snapshot properties
-    snapshot.forceClientComposition = snapshot.isHdrY410 || snapshot.shadowSettings.length > 0 ||
-            requested.blurRegions.size() > 0 || snapshot.stretchEffect.hasEffect();
+    snapshot.forceClientComposition =
+            snapshot.shadowSettings.length > 0 || snapshot.stretchEffect.hasEffect();
     snapshot.contentOpaque = snapshot.isContentOpaque();
     snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() &&
             snapshot.color.a == 1.f;
@@ -870,7 +894,12 @@
 
 void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot,
                                                const RequestedLayerState& requested,
-                                               const LayerSnapshot& parentSnapshot) {
+                                               const LayerSnapshot& parentSnapshot,
+                                               const Args& args) {
+    if (args.skipRoundCornersWhenProtected && requested.isProtected()) {
+        snapshot.roundedCorner = RoundedCornerState();
+        return;
+    }
     snapshot.roundedCorner = RoundedCornerState();
     RoundedCornerState parentRoundedCorner;
     if (parentSnapshot.roundedCorner.hasRoundedCorners()) {
@@ -966,9 +995,12 @@
 }
 
 void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot, const RequestedLayerState&,
-                                         const renderengine::ShadowSettings& globalShadowSettings) {
-    if (snapshot.shadowRadius > 0.f) {
-        snapshot.shadowSettings = globalShadowSettings;
+                                         const ShadowSettings& globalShadowSettings) {
+    if (snapshot.shadowSettings.length > 0.f) {
+        snapshot.shadowSettings.ambientColor = globalShadowSettings.ambientColor;
+        snapshot.shadowSettings.spotColor = globalShadowSettings.spotColor;
+        snapshot.shadowSettings.lightPos = globalShadowSettings.lightPos;
+        snapshot.shadowSettings.lightRadius = globalShadowSettings.lightRadius;
 
         // Note: this preserves existing behavior of shadowing the entire layer and not cropping
         // it if transparent regions are present. This may not be necessary since shadows are
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index c81a5d2..1506913 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -47,13 +47,14 @@
         const DisplayInfos& displays;
         // Set to true if there were display changes since last update.
         bool displayChanges = false;
-        const renderengine::ShadowSettings& globalShadowSettings;
+        const ShadowSettings& globalShadowSettings;
         bool supportsBlur = true;
         bool forceFullDamage = false;
         std::optional<FloatRect> parentCrop = std::nullopt;
         std::unordered_set<uint32_t> excludeLayerIds;
         const std::unordered_map<std::string, bool>& supportedLayerGenericMetadata;
         const std::unordered_map<std::string, uint32_t>& genericLayerMetadataKeyMap;
+        bool skipRoundCornersWhenProtected = false;
     };
     LayerSnapshotBuilder();
 
@@ -103,11 +104,11 @@
                                     bool parentIsRelative, const Args& args);
     static void resetRelativeState(LayerSnapshot& snapshot);
     static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
-                                    const LayerSnapshot& parentSnapshot);
+                                    const LayerSnapshot& parentSnapshot, const Args& args);
     void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
                            const LayerSnapshot& parentSnapshot, uint32_t displayRotationFlags);
     static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested,
-                              const renderengine::ShadowSettings& globalShadowSettings);
+                              const ShadowSettings& globalShadowSettings);
     void updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested,
                      const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath& path,
                      const Args& args);
@@ -116,8 +117,8 @@
     LayerSnapshot* createSnapshot(const LayerHierarchy::TraversalPath& id,
                                   const RequestedLayerState& layer,
                                   const LayerSnapshot& parentSnapshot);
-    void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot,
-                          const Args& args);
+    void updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
+                                          const LayerSnapshot& childSnapshot, const Args& args);
     void updateTouchableRegionCrop(const Args& args);
 
     std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*,
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 5738262..0e49b75 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -19,10 +19,13 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
+#include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 #include <sys/types.h>
 
+#include <scheduler/Fps.h>
+
 #include "Layer.h"
 #include "LayerCreationArgs.h"
 #include "LayerLog.h"
@@ -50,7 +53,6 @@
         name(args.name + "#" + std::to_string(args.sequence)),
         canBeRoot(args.addToRoot),
         layerCreationFlags(args.flags),
-        textureName(args.textureName),
         ownerUid(args.ownerUid),
         ownerPid(args.ownerPid),
         parentId(args.parentId),
@@ -120,12 +122,25 @@
     isTrustedOverlay = false;
     dropInputMode = gui::DropInputMode::NONE;
     dimmingEnabled = true;
-    defaultFrameRateCompatibility =
-            static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
+    defaultFrameRateCompatibility = static_cast<int8_t>(scheduler::FrameRateCompatibility::Default);
+    frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
+    frameRateCategorySmoothSwitchOnly = false;
+    frameRateSelectionStrategy =
+            static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
     dataspace = ui::Dataspace::V0_SRGB;
     gameMode = gui::GameMode::Unsupported;
     requestedFrameRate = {};
     cachingHint = gui::CachingHint::Enabled;
+
+    if (name.length() > 77) {
+        std::string shortened;
+        shortened.append(name, 0, 36);
+        shortened.append("[...]");
+        shortened.append(name, name.length() - 36);
+        debugName = std::move(shortened);
+    } else {
+        debugName = name;
+    }
 }
 
 void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
@@ -136,16 +151,20 @@
     const ui::Size oldBufferSize = hadBuffer
             ? ui::Size(externalTexture->getWidth(), externalTexture->getHeight())
             : ui::Size();
+    const uint64_t oldUsageFlags = hadBuffer ? externalTexture->getUsage() : 0;
+    const bool oldBufferFormatOpaque = LayerSnapshot::isOpaqueFormat(
+            externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE);
+
     const bool hadSideStream = sidebandStream != nullptr;
     const layer_state_t& clientState = resolvedComposerState.state;
-    const bool hadBlur = hasBlur();
+    const bool hadSomethingToDraw = hasSomethingToDraw();
     uint64_t clientChanges = what | layer_state_t::diff(clientState);
     layer_state_t::merge(clientState);
     what = clientChanges;
     LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges);
 
     if (clientState.what & layer_state_t::eFlagsChanged) {
-        if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) {
+        if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) {
             changes |= RequestedLayerState::Changes::Visibility |
                     RequestedLayerState::Changes::VisibleRegion;
         }
@@ -166,6 +185,10 @@
                 changes |= RequestedLayerState::Changes::BufferSize;
                 changes |= RequestedLayerState::Changes::Geometry;
             }
+            const uint64_t usageFlags = hasBuffer ? externalTexture->getUsage() : 0;
+            if (oldUsageFlags != usageFlags) {
+                changes |= RequestedLayerState::Changes::BufferUsageFlags;
+            }
         }
 
         if (hasBuffer != hadBuffer) {
@@ -186,7 +209,7 @@
                  (barrierFrameNumber > bufferData->frameNumber))) {
                 ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
                       " -> producedId=%d frameNumber=%" PRIu64,
-                      getDebugString().c_str(), bufferData->producerId, bufferData->frameNumber,
+                      getDebugString().c_str(), barrierProducerId, barrierFrameNumber,
                       bufferData->producerId, frameNumber);
                 TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
                                                              /*overwrite=*/false);
@@ -195,6 +218,13 @@
             barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
             barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
         }
+
+        const bool newBufferFormatOpaque = LayerSnapshot::isOpaqueFormat(
+                externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE);
+        if (newBufferFormatOpaque != oldBufferFormatOpaque) {
+            changes |= RequestedLayerState::Changes::Visibility |
+                    RequestedLayerState::Changes::VisibleRegion;
+        }
     }
 
     if (clientState.what & layer_state_t::eSidebandStreamChanged) {
@@ -208,15 +238,13 @@
     }
     if (what & (layer_state_t::eAlphaChanged)) {
         if (oldAlpha == 0 || color.a == 0) {
-            changes |= RequestedLayerState::Changes::Visibility |
-                    RequestedLayerState::Changes::VisibleRegion;
+            changes |= RequestedLayerState::Changes::Visibility;
         }
     }
-    if (what & (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged)) {
-        if (hadBlur != hasBlur()) {
-            changes |= RequestedLayerState::Changes::Visibility |
-                    RequestedLayerState::Changes::VisibleRegion;
-        }
+
+    if (hadSomethingToDraw != hasSomethingToDraw()) {
+        changes |= RequestedLayerState::Changes::Visibility |
+                RequestedLayerState::Changes::VisibleRegion;
     }
     if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
         changes |= RequestedLayerState::Changes::Hierarchy;
@@ -302,8 +330,14 @@
                 Layer::FrameRate::convertCompatibility(clientState.frameRateCompatibility);
         const auto strategy = Layer::FrameRate::convertChangeFrameRateStrategy(
                 clientState.changeFrameRateStrategy);
-        requestedFrameRate =
-                Layer::FrameRate(Fps::fromValue(clientState.frameRate), compatibility, strategy);
+        requestedFrameRate.vote =
+                Layer::FrameRate::FrameRateVote(Fps::fromValue(clientState.frameRate),
+                                                compatibility, strategy);
+        changes |= RequestedLayerState::Changes::FrameRate;
+    }
+    if (clientState.what & layer_state_t::eFrameRateCategoryChanged) {
+        const auto category = Layer::FrameRate::convertCategory(clientState.frameRateCategory);
+        requestedFrameRate.category = category;
         changes |= RequestedLayerState::Changes::FrameRate;
     }
 }
@@ -367,9 +401,26 @@
     if (!handleAlive) debug << " !handle";
     if (z != 0) debug << " z=" << z;
     if (layerStack.id != 0) debug << " layerStack=" << layerStack.id;
+    debug << "}";
     return debug.str();
 }
 
+std::ostream& operator<<(std::ostream& out, const scheduler::LayerInfo::FrameRate& obj) {
+    out << obj.vote.rate;
+    out << " " << ftl::enum_string_full(obj.vote.type);
+    out << " " << ftl::enum_string_full(obj.category);
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const RequestedLayerState& obj) {
+    out << obj.debugName;
+    if (obj.relativeParentId != UNASSIGNED_LAYER_ID) out << " parent=" << obj.parentId;
+    if (!obj.handleAlive) out << " handleNotAlive";
+    if (obj.requestedFrameRate.isValid())
+        out << " requestedFrameRate: {" << obj.requestedFrameRate << "}";
+    return out;
+}
+
 std::string RequestedLayerState::getDebugStringShort() const {
     return "[" + std::to_string(id) + "]" + name;
 }
@@ -426,12 +477,18 @@
 Rect RequestedLayerState::getBufferCrop() const {
     // this is the crop rectangle that applies to the buffer
     // itself (as opposed to the window)
-    if (!bufferCrop.isEmpty()) {
-        // if the buffer crop is defined, we use that
-        return bufferCrop;
+    if (!bufferCrop.isEmpty() && externalTexture != nullptr) {
+        // if the buffer crop is defined and there's a valid buffer, intersect buffer size and crop
+        // since the crop should never exceed the size of the buffer.
+        Rect sizeAndCrop;
+        externalTexture->getBounds().intersect(bufferCrop, &sizeAndCrop);
+        return sizeAndCrop;
     } else if (externalTexture != nullptr) {
         // otherwise we use the whole buffer
         return externalTexture->getBounds();
+    } else if (!bufferCrop.isEmpty()) {
+        // if the buffer crop is defined, we use that
+        return bufferCrop;
     } else {
         // if we don't have a buffer yet, we use an empty/invalid crop
         return Rect();
@@ -451,6 +508,9 @@
     if (flags & layer_state_t::eLayerIsDisplayDecoration) {
         return Composition::DISPLAY_DECORATION;
     }
+    if (flags & layer_state_t::eLayerIsRefreshRateIndicator) {
+        return Composition::REFRESH_RATE_INDICATOR;
+    }
     if (potentialCursor) {
         return Composition::CURSOR;
     }
@@ -507,6 +567,61 @@
     return changes.test(Changes::Buffer) && !externalTexture;
 }
 
+bool RequestedLayerState::backpressureEnabled() const {
+    return flags & layer_state_t::eEnableBackpressure;
+}
+
+bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const {
+    static constexpr uint64_t requiredFlags = layer_state_t::eBufferChanged;
+    if ((s.what & requiredFlags) != requiredFlags) {
+        ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+                              (s.what | requiredFlags) & ~s.what);
+        return false;
+    }
+
+    static constexpr uint64_t deniedFlags = layer_state_t::eProducerDisconnect |
+            layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged |
+            layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged |
+            layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged |
+            layer_state_t::eAutoRefreshChanged | layer_state_t::eReparent;
+    if (s.what & deniedFlags) {
+        ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+                              s.what & deniedFlags);
+        return false;
+    }
+
+    bool changedFlags = diff(s);
+    static constexpr auto deniedChanges = layer_state_t::ePositionChanged |
+            layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged |
+            layer_state_t::eBackgroundColorChanged | layer_state_t::eMatrixChanged |
+            layer_state_t::eCornerRadiusChanged | layer_state_t::eBackgroundBlurRadiusChanged |
+            layer_state_t::eBufferTransformChanged |
+            layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
+            layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged |
+            layer_state_t::eSidebandStreamChanged | layer_state_t::eColorSpaceAgnosticChanged |
+            layer_state_t::eShadowRadiusChanged | layer_state_t::eFixedTransformHintChanged |
+            layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged |
+            layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged |
+            layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged;
+    if (changedFlags & deniedChanges) {
+        ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
+                              s.what & deniedChanges);
+        return false;
+    }
+
+    return true;
+}
+
+bool RequestedLayerState::isProtected() const {
+    return externalTexture && externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED;
+}
+
+bool RequestedLayerState::hasSomethingToDraw() const {
+    return externalTexture != nullptr || sidebandStream != nullptr || shadowRadius > 0.f ||
+            backgroundBlurRadius > 0 || blurRegions.size() > 0 ||
+            (color.r >= 0.0_hf && color.g >= 0.0_hf && color.b >= 0.0_hf);
+}
+
 void RequestedLayerState::clearChanges() {
     what = 0;
     changes.clear();
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 02e3bac..09f33de 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -56,6 +56,7 @@
         Animation = 1u << 17,
         BufferSize = 1u << 18,
         GameMode = 1u << 19,
+        BufferUsageFlags = 1u << 20,
     };
     static Rect reduce(const Rect& win, const Region& exclude);
     RequestedLayerState(const LayerCreationArgs&);
@@ -74,6 +75,7 @@
     Rect getBufferCrop() const;
     std::string getDebugString() const;
     std::string getDebugStringShort() const;
+    friend std::ostream& operator<<(std::ostream& os, const RequestedLayerState& obj);
     aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
     bool hasValidRelativeParent() const;
     bool hasInputInfo() const;
@@ -82,6 +84,10 @@
     bool hasReadyFrame() const;
     bool hasSidebandStreamFrame() const;
     bool willReleaseBufferOnLatch() const;
+    bool backpressureEnabled() const;
+    bool isSimpleBufferUpdate(const layer_state_t&) const;
+    bool isProtected() const;
+    bool hasSomethingToDraw() const;
 
     // Layer serial number.  This gives layers an explicit ordering, so we
     // have a stable sort order when their layer stack and Z-order are
@@ -90,7 +96,6 @@
     const std::string name;
     bool canBeRoot = false;
     const uint32_t layerCreationFlags;
-    const uint32_t textureName;
     // The owner of the layer. If created from a non system process, it will be the calling uid.
     // If created from a system process, the value can be passed in.
     const gui::Uid ownerUid;
@@ -116,6 +121,7 @@
     uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
     uint64_t barrierFrameNumber = 0;
     uint32_t barrierProducerId = 0;
+    std::string debugName;
 
     // book keeping states
     bool handleAlive = true;
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 6e78e93..d3d9509 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -22,6 +22,7 @@
 #include <cutils/trace.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
+#include "FrontEnd/LayerLog.h"
 
 #include "TransactionHandler.h"
 
@@ -33,7 +34,7 @@
     ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
 }
 
-std::vector<TransactionState> TransactionHandler::flushTransactions() {
+void TransactionHandler::collectTransactions() {
     while (!mLocklessTransactionQueue.isEmpty()) {
         auto maybeTransaction = mLocklessTransactionQueue.pop();
         if (!maybeTransaction.has_value()) {
@@ -42,7 +43,9 @@
         auto transaction = maybeTransaction.value();
         mPendingTransactionQueues[transaction.applyToken].emplace(std::move(transaction));
     }
+}
 
+std::vector<TransactionState> TransactionHandler::flushTransactions() {
     // Collect transaction that are ready to be applied.
     std::vector<TransactionState> transactions;
     TransactionFlushState flushState;
@@ -85,8 +88,8 @@
     }
 
     auto it = mPendingTransactionQueues.find(flushState.queueWithUnsignaledBuffer);
-    LOG_ALWAYS_FATAL_IF(it == mPendingTransactionQueues.end(),
-                        "Could not find queue with unsignaled buffer!");
+    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mPendingTransactionQueues.end(),
+                                    "Could not find queue with unsignaled buffer!");
 
     auto& queue = it->second;
     popTransactionFromPending(transactions, flushState, queue);
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index ff54dc5..00f6bce 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -59,6 +59,8 @@
     using TransactionFilter = std::function<TransactionReadiness(const TransactionFlushState&)>;
 
     bool hasPendingTransactions();
+    // Moves transactions from the lockless queue.
+    void collectTransactions();
     std::vector<TransactionState> flushTransactions();
     void addTransactionReadyFilter(TransactionFilter&&);
     void queueTransaction(TransactionState&&);
diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h
index e1449b6..e5cca8f 100644
--- a/services/surfaceflinger/FrontEnd/Update.h
+++ b/services/surfaceflinger/FrontEnd/Update.h
@@ -46,7 +46,7 @@
     std::vector<LayerCreatedState> layerCreatedStates;
     std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
     std::vector<LayerCreationArgs> layerCreationArgs;
-    std::vector<uint32_t> destroyedHandles;
+    std::vector<std::pair<uint32_t, std::string /* debugName */>> destroyedHandles;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.cpp b/services/surfaceflinger/HdrLayerInfoReporter.cpp
index 9eefbe4..2788332 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.cpp
+++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp
@@ -18,14 +18,22 @@
 #define LOG_TAG "HdrLayerInfoReporter"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <android-base/stringprintf.h>
+#include <inttypes.h>
 #include <utils/Trace.h>
 
 #include "HdrLayerInfoReporter.h"
 
 namespace android {
 
+using base::StringAppendF;
+
 void HdrLayerInfoReporter::dispatchHdrLayerInfo(const HdrLayerInfo& info) {
     ATRACE_CALL();
+    if (mHdrInfoHistory.size() == 0 || mHdrInfoHistory.back().info != info) {
+        mHdrInfoHistory.next() = EventHistoryEntry{info};
+    }
+
     std::vector<sp<gui::IHdrLayerInfoListener>> toInvoke;
     {
         std::scoped_lock lock(mMutex);
@@ -62,4 +70,15 @@
     mListeners.erase(wp<IBinder>(IInterface::asBinder(listener)));
 }
 
+void HdrLayerInfoReporter::dump(std::string& result) const {
+    for (size_t i = 0; i < mHdrInfoHistory.size(); i++) {
+        const auto& event = mHdrInfoHistory[i];
+        const auto& info = event.info;
+        StringAppendF(&result,
+                      "%" PRId64 ": numHdrLayers(%d), size(%dx%d), flags(%X), desiredRatio(%.2f)\n",
+                      event.timestamp, info.numberOfHdrLayers, info.maxW, info.maxH, info.flags,
+                      info.maxDesiredHdrSdrRatio);
+    }
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h
index bf7c775..614f33f 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.h
+++ b/services/surfaceflinger/HdrLayerInfoReporter.h
@@ -19,9 +19,11 @@
 #include <android-base/thread_annotations.h>
 #include <android/gui/IHdrLayerInfoListener.h>
 #include <binder/IBinder.h>
+#include <utils/Timers.h>
 
 #include <unordered_map>
 
+#include "Utils/RingBuffer.h"
 #include "WpHash.h"
 
 namespace android {
@@ -79,6 +81,8 @@
         return !mListeners.empty();
     }
 
+    void dump(std::string& result) const;
+
 private:
     mutable std::mutex mMutex;
 
@@ -88,6 +92,17 @@
     };
 
     std::unordered_map<wp<IBinder>, TrackedListener, WpHash> mListeners GUARDED_BY(mMutex);
+
+    struct EventHistoryEntry {
+        nsecs_t timestamp = -1;
+        HdrLayerInfo info;
+
+        EventHistoryEntry() {}
+
+        EventHistoryEntry(const HdrLayerInfo& info) : info(info) { timestamp = systemTime(); }
+    };
+
+    utils::RingBuffer<EventHistoryEntry, 32> mHdrInfoHistory;
 };
 
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.cpp b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
new file mode 100644
index 0000000..dfb1c1e
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
@@ -0,0 +1,202 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// #define LOG_NDEBUG 0
+#include <algorithm>
+
+#include "HdrSdrRatioOverlay.h"
+
+#include <SkSurface.h>
+
+#undef LOG_TAG
+#define LOG_TAG "HdrSdrRatioOverlay"
+
+namespace android {
+
+void HdrSdrRatioOverlay::drawNumber(float number, int left, SkColor color, SkCanvas& canvas) {
+    if (!isfinite(number) || number >= 10.f) return;
+    // We assume that the number range is [1.f, 10.f)
+    // and the decimal places are 2.
+    int value = static_cast<int>(number * 100);
+    SegmentDrawer::drawDigit(value / 100, left, color, canvas);
+
+    left += kDigitWidth + kDigitSpace;
+    SegmentDrawer::drawSegment(SegmentDrawer::Segment::DecimalPoint, left, color, canvas);
+    left += kDigitWidth + kDigitSpace;
+
+    SegmentDrawer::drawDigit((value / 10) % 10, left, color, canvas);
+    left += kDigitWidth + kDigitSpace;
+    SegmentDrawer::drawDigit(value % 10, left, color, canvas);
+}
+
+sp<GraphicBuffer> HdrSdrRatioOverlay::draw(float currentHdrSdrRatio, SkColor color,
+                                           ui::Transform::RotationFlags rotation,
+                                           sp<GraphicBuffer>& ringBuffer) {
+    const int32_t bufferWidth = kBufferWidth;
+    const int32_t bufferHeight = kBufferWidth;
+
+    const auto kUsageFlags = static_cast<uint64_t>(
+            GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_TEXTURE);
+
+    // ring buffers here to do double-buffered rendering to avoid
+    // possible tearing and also to reduce memory take-up.
+    if (ringBuffer == nullptr) {
+        ringBuffer = sp<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+                                             static_cast<uint32_t>(bufferHeight),
+                                             HAL_PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags,
+                                             "HdrSdrRatioOverlayBuffer");
+    }
+
+    auto& buffer = ringBuffer;
+
+    SkMatrix canvasTransform = SkMatrix();
+    switch (rotation) {
+        case ui::Transform::ROT_90:
+            canvasTransform.setTranslate(bufferHeight, 0);
+            canvasTransform.preRotate(90.f);
+            break;
+        case ui::Transform::ROT_270:
+            canvasTransform.setRotate(270.f, bufferWidth / 2.f, bufferWidth / 2.f);
+            break;
+        default:
+            break;
+    }
+
+    const status_t bufferStatus = buffer->initCheck();
+    LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "HdrSdrRatioOverlay: Buffer failed to allocate: %d",
+                        bufferStatus);
+
+    sk_sp<SkSurface> surface =
+            SkSurfaces::Raster(SkImageInfo::MakeN32Premul(bufferWidth, bufferHeight));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->setMatrix(canvasTransform);
+
+    drawNumber(currentHdrSdrRatio, 0, color, *canvas);
+
+    void* pixels = nullptr;
+    buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
+
+    const SkImageInfo& imageInfo = surface->imageInfo();
+    const size_t dstRowBytes = buffer->getStride() * static_cast<size_t>(imageInfo.bytesPerPixel());
+
+    canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
+    buffer->unlock();
+    return buffer;
+}
+
+std::unique_ptr<HdrSdrRatioOverlay> HdrSdrRatioOverlay::create() {
+    std::unique_ptr<HdrSdrRatioOverlay> overlay =
+            std::make_unique<HdrSdrRatioOverlay>(ConstructorTag{});
+    if (overlay->initCheck()) {
+        return overlay;
+    }
+
+    ALOGE("%s: Failed to create HdrSdrRatioOverlay", __func__);
+    return {};
+}
+
+HdrSdrRatioOverlay::HdrSdrRatioOverlay(ConstructorTag)
+      : mSurfaceControl(
+                SurfaceControlHolder::createSurfaceControlHolder(String8("HdrSdrRatioOverlay"))) {
+    if (!mSurfaceControl) {
+        ALOGE("%s: Failed to create buffer state layer", __func__);
+        return;
+    }
+    SurfaceComposerClient::Transaction()
+            .setLayer(mSurfaceControl->get(), INT32_MAX - 2)
+            .setTrustedOverlay(mSurfaceControl->get(), true)
+            .apply();
+}
+
+bool HdrSdrRatioOverlay::initCheck() const {
+    return mSurfaceControl != nullptr;
+}
+
+void HdrSdrRatioOverlay::changeHdrSdrRatio(float currentHdrSdrRatio) {
+    mCurrentHdrSdrRatio = currentHdrSdrRatio;
+    animate();
+}
+
+void HdrSdrRatioOverlay::setLayerStack(ui::LayerStack stack) {
+    SurfaceComposerClient::Transaction().setLayerStack(mSurfaceControl->get(), stack).apply();
+}
+
+void HdrSdrRatioOverlay::setViewport(ui::Size viewport) {
+    constexpr int32_t kMaxWidth = 1000;
+    const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
+    const auto height = 2 * width;
+    Rect frame((5 * width) >> 4, height >> 5);
+    // set the ratio frame to the top right of the screen
+    frame.offsetBy(viewport.width - frame.width(), height >> 4);
+
+    SurfaceComposerClient::Transaction()
+            .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
+                       0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
+            .setPosition(mSurfaceControl->get(), frame.left, frame.top)
+            .apply();
+}
+
+auto HdrSdrRatioOverlay::getOrCreateBuffers(float currentHdrSdrRatio) -> const sp<GraphicBuffer> {
+    static const sp<GraphicBuffer> kNoBuffer;
+    if (!mSurfaceControl) return kNoBuffer;
+
+    const auto transformHint =
+            static_cast<ui::Transform::RotationFlags>(mSurfaceControl->get()->getTransformHint());
+
+    // Tell SurfaceFlinger about the pre-rotation on the buffer.
+    const auto transform = [&] {
+        switch (transformHint) {
+            case ui::Transform::ROT_90:
+                return ui::Transform::ROT_270;
+            case ui::Transform::ROT_270:
+                return ui::Transform::ROT_90;
+            default:
+                return ui::Transform::ROT_0;
+        }
+    }();
+
+    SurfaceComposerClient::Transaction().setTransform(mSurfaceControl->get(), transform).apply();
+
+    constexpr SkColor kMinRatioColor = SK_ColorBLUE;
+    constexpr SkColor kMaxRatioColor = SK_ColorGREEN;
+    constexpr float kAlpha = 0.8f;
+
+    // 9.f is picked here as ratio range, given that we assume that
+    // hdr/sdr ratio is [1.f, 10.f)
+    const float scale = currentHdrSdrRatio / 9.f;
+
+    SkColor4f colorBase = SkColor4f::FromColor(kMaxRatioColor) * scale;
+    const SkColor4f minRatioColor = SkColor4f::FromColor(kMinRatioColor) * (1 - scale);
+
+    colorBase.fR = colorBase.fR + minRatioColor.fR;
+    colorBase.fG = colorBase.fG + minRatioColor.fG;
+    colorBase.fB = colorBase.fB + minRatioColor.fB;
+    colorBase.fA = kAlpha;
+
+    const SkColor color = colorBase.toSkColor();
+
+    auto buffer = draw(currentHdrSdrRatio, color, transformHint, mRingBuffer[mIndex]);
+    mIndex = (mIndex + 1) % 2;
+    return buffer;
+}
+
+void HdrSdrRatioOverlay::animate() {
+    if (!std::isfinite(mCurrentHdrSdrRatio) || mCurrentHdrSdrRatio < 1.0f) return;
+    SurfaceComposerClient::Transaction()
+            .setBuffer(mSurfaceControl->get(), getOrCreateBuffers(mCurrentHdrSdrRatio))
+            .apply();
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.h b/services/surfaceflinger/HdrSdrRatioOverlay.h
new file mode 100644
index 0000000..72d401d
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.h
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Utils/OverlayUtils.h"
+
+#include <ui/Size.h>
+#include <utils/StrongPointer.h>
+
+class SkCanvas;
+
+namespace android {
+class HdrSdrRatioOverlay {
+private:
+    // Effectively making the constructor private, while keeping std::make_unique work
+    struct ConstructorTag {};
+
+public:
+    static std::unique_ptr<HdrSdrRatioOverlay> create();
+
+    void setLayerStack(ui::LayerStack);
+    void setViewport(ui::Size);
+    void animate();
+    void changeHdrSdrRatio(float currentRatio);
+
+    HdrSdrRatioOverlay(ConstructorTag);
+
+private:
+    bool initCheck() const;
+
+    static sp<GraphicBuffer> draw(float currentHdrSdrRatio, SkColor, ui::Transform::RotationFlags,
+                                  sp<GraphicBuffer>& ringBufer);
+    static void drawNumber(float number, int left, SkColor, SkCanvas&);
+
+    const sp<GraphicBuffer> getOrCreateBuffers(float currentHdrSdrRatio);
+
+    float mCurrentHdrSdrRatio = 1.f;
+    const std::unique_ptr<SurfaceControlHolder> mSurfaceControl;
+
+    size_t mIndex = 0;
+    std::array<sp<GraphicBuffer>, 2> mRingBuffer;
+};
+} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 9c5e5e2..700baa2 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -27,7 +27,6 @@
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
-#include <android/native_window.h>
 #include <binder/IPCThreadState.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
@@ -101,7 +100,7 @@
     using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility;
     using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness;
     const auto frameRateCompatibility = [frameRate] {
-        switch (frameRate.type) {
+        switch (frameRate.vote.type) {
             case Layer::FrameRateCompatibility::Default:
                 return FrameRateCompatibility::Default;
             case Layer::FrameRateCompatibility::ExactOrMultiple:
@@ -112,7 +111,7 @@
     }();
 
     const auto seamlessness = [frameRate] {
-        switch (frameRate.seamlessness) {
+        switch (frameRate.vote.seamlessness) {
             case scheduler::Seamlessness::OnlySeamless:
                 return Seamlessness::ShouldBeSeamless;
             case scheduler::Seamlessness::SeamedAndSeamless:
@@ -122,7 +121,7 @@
         }
     }();
 
-    return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(),
+    return TimeStats::SetFrameRateVote{.frameRate = frameRate.vote.rate.getValue(),
                                        .frameRateCompatibility = frameRateCompatibility,
                                        .seamlessness = seamlessness};
 }
@@ -140,7 +139,7 @@
 
 using PresentState = frametimeline::SurfaceFrame::PresentState;
 
-Layer::Layer(const LayerCreationArgs& args)
+Layer::Layer(const surfaceflinger::LayerCreationArgs& args)
       : sequence(args.sequence),
         mFlinger(sp<SurfaceFlinger>::fromExisting(args.flinger)),
         mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)),
@@ -149,7 +148,6 @@
                 args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
         mLayerCreationFlags(args.flags),
         mBorderEnabled(false),
-        mTextureName(args.textureName),
         mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
     ALOGV("Creating Layer %s", getDebugName());
 
@@ -193,6 +191,7 @@
     mDrawingState.dropInputMode = gui::DropInputMode::NONE;
     mDrawingState.dimmingEnabled = true;
     mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
+    mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Self;
 
     if (args.flags & ISurfaceComposerClient::eNoColorFill) {
         // Set an invalid color so there is no color fill.
@@ -206,6 +205,7 @@
 
     mOwnerUid = args.ownerUid;
     mOwnerPid = args.ownerPid;
+    mOwnerAppId = mOwnerUid % PER_USER_RANGE;
 
     mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
     mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
@@ -213,7 +213,6 @@
 
     mSnapshot->sequence = sequence;
     mSnapshot->name = getDebugName();
-    mSnapshot->textureName = mTextureName;
     mSnapshot->premultipliedAlpha = mPremultipliedAlpha;
     mSnapshot->parentTransform = {};
 }
@@ -235,13 +234,6 @@
                                   mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber,
                                   mBufferInfo.mFence);
     }
-    if (!isClone()) {
-        // The original layer and the clone layer share the same texture. Therefore, only one of
-        // the layers, in this case the original layer, needs to handle the deletion. The original
-        // layer and the clone should be removed at the same time so there shouldn't be any issue
-        // with the clone layer trying to use the deleted texture.
-        mFlinger->deleteTextureAsync(mTextureName);
-    }
     const int32_t layerId = getSequence();
     mFlinger->mTimeStats->onDestroy(layerId);
     mFlinger->mFrameTracer->onDestroy(layerId);
@@ -401,7 +393,7 @@
     mLastComputedTrustedPresentationState = false;
 
     if (!leaveState) {
-        const auto outputLayer = findOutputLayerForDisplay(display);
+        const auto outputLayer = findOutputLayerForDisplay(display, snapshot->path);
         if (outputLayer != nullptr) {
             if (outputLayer->getState().coveredRegionExcludingDisplayOverlays) {
                 Region coveredRegion =
@@ -580,7 +572,7 @@
     snapshot->outputFilter = getOutputFilter();
     snapshot->isVisible = isVisible();
     snapshot->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f;
-    snapshot->shadowRadius = mEffectiveShadowRadius;
+    snapshot->shadowSettings.length = mEffectiveShadowRadius;
 
     snapshot->contentDirty = contentDirty;
     contentDirty = false;
@@ -593,8 +585,8 @@
     snapshot->localTransformInverse = snapshot->localTransform.inverse();
     snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
     snapshot->alpha = alpha;
-    snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius;
-    snapshot->blurRegions = drawingState.blurRegions;
+    snapshot->backgroundBlurRadius = getBackgroundBlurRadius();
+    snapshot->blurRegions = getBlurRegions();
     snapshot->stretchEffect = getStretchEffect();
 }
 
@@ -658,13 +650,12 @@
     // Force client composition for special cases known only to the front-end.
     // Rounded corners no longer force client composition, since we may use a
     // hole punch so that the layer will appear to have rounded corners.
-    if (isHdrY410() || drawShadows() || drawingState.blurRegions.size() > 0 ||
-        snapshot->stretchEffect.hasEffect()) {
+    if (drawShadows() || snapshot->stretchEffect.hasEffect()) {
         snapshot->forceClientComposition = true;
     }
     // If there are no visible region changes, we still need to update blur parameters.
-    snapshot->blurRegions = drawingState.blurRegions;
-    snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius;
+    snapshot->blurRegions = getBlurRegions();
+    snapshot->backgroundBlurRadius = getBackgroundBlurRadius();
 
     // Layer framerate is used in caching decisions.
     // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in
@@ -740,6 +731,11 @@
 aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType(
         const DisplayDevice& display) const {
     const auto outputLayer = findOutputLayerForDisplay(&display);
+    return getCompositionType(outputLayer);
+}
+
+aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType(
+        const compositionengine::OutputLayer* outputLayer) const {
     if (outputLayer == nullptr) {
         return aidl::android::hardware::graphics::composer3::Composition::INVALID;
     }
@@ -791,8 +787,9 @@
         if (includeJankData) {
             std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
                     mPendingJankClassifications.front();
-            jankData.emplace_back(
-                    JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
+            jankData.emplace_back(JankData(surfaceFrame->getToken(),
+                                           surfaceFrame->getJankType().value(),
+                                           surfaceFrame->getRenderRate().getPeriodNsecs()));
         }
         mPendingJankClassifications.pop_front();
     }
@@ -830,12 +827,12 @@
         mFlinger->mUpdateInputInfo = true;
     }
 
-    commitTransaction(mDrawingState);
+    commitTransaction();
 
     return flags;
 }
 
-void Layer::commitTransaction(State&) {
+void Layer::commitTransaction() {
     // Set the present state for all bufferlessSurfaceFramesTX to Presented. The
     // bufferSurfaceFrameTX will be presented in latchBuffer.
     for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) {
@@ -1005,8 +1002,8 @@
         uint32_t flags = ISurfaceComposerClient::eFXSurfaceEffect;
         std::string name = mName + "BackgroundColorLayer";
         mDrawingState.bgColorLayer = mFlinger->getFactory().createEffectLayer(
-                LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), flags,
-                                  LayerMetadata()));
+                surfaceflinger::LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), flags,
+                                                  LayerMetadata()));
 
         // add to child list
         addChild(mDrawingState.bgColorLayer);
@@ -1159,12 +1156,12 @@
     if (mDrawingState.defaultFrameRateCompatibility == compatibility) return false;
     mDrawingState.defaultFrameRateCompatibility = compatibility;
     mDrawingState.modified = true;
-    mFlinger->mScheduler->setDefaultFrameRateCompatibility(this);
+    mFlinger->mScheduler->setDefaultFrameRateCompatibility(sequence, compatibility);
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
 
-scheduler::LayerInfo::FrameRateCompatibility Layer::getDefaultFrameRateCompatibility() const {
+scheduler::FrameRateCompatibility Layer::getDefaultFrameRateCompatibility() const {
     return mDrawingState.defaultFrameRateCompatibility;
 }
 
@@ -1256,43 +1253,52 @@
     return mBorderColor;
 }
 
-bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded) {
-    // The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate
+bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+                                           bool* transactionNeeded) {
+    // Gets the frame rate to propagate to children.
     const auto frameRate = [&] {
-        if (mDrawingState.frameRate.rate.isValid() ||
-            mDrawingState.frameRate.type == FrameRateCompatibility::NoVote) {
+        if (overrideChildren && parentFrameRate.isValid()) {
+            return parentFrameRate;
+        }
+
+        if (mDrawingState.frameRate.isValid()) {
             return mDrawingState.frameRate;
         }
 
         return parentFrameRate;
     }();
 
-    *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate);
+    auto now = systemTime();
+    *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate, now);
 
     // The frame rate is propagated to the children
     bool childrenHaveFrameRate = false;
     for (const sp<Layer>& child : mCurrentChildren) {
         childrenHaveFrameRate |=
-                child->propagateFrameRateForLayerTree(frameRate, transactionNeeded);
+                child->propagateFrameRateForLayerTree(frameRate,
+                                                      overrideChildren ||
+                                                              shouldOverrideChildrenFrameRate(),
+                                                      transactionNeeded);
     }
 
-    // If we don't have a valid frame rate, but the children do, we set this
+    // If we don't have a valid frame rate specification, but the children do, we set this
     // layer as NoVote to allow the children to control the refresh rate
-    if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote &&
-        childrenHaveFrameRate) {
+    if (!frameRate.isValid() && childrenHaveFrameRate) {
         *transactionNeeded |=
-                setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote));
+                setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote),
+                                               now);
     }
 
-    // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
+    // We return whether this layer or its children has a vote. We ignore ExactOrMultiple votes for
     // the same reason we are allowing touch boost for those layers. See
     // RefreshRateSelector::rankFrameRates for details.
     const auto layerVotedWithDefaultCompatibility =
-            frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Default;
-    const auto layerVotedWithNoVote = frameRate.type == FrameRateCompatibility::NoVote;
+            frameRate.vote.rate.isValid() && frameRate.vote.type == FrameRateCompatibility::Default;
+    const auto layerVotedWithNoVote = frameRate.vote.type == FrameRateCompatibility::NoVote;
+    const auto layerVotedWithCategory = frameRate.category != FrameRateCategory::Default;
     const auto layerVotedWithExactCompatibility =
-            frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Exact;
-    return layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
+            frameRate.vote.rate.isValid() && frameRate.vote.type == FrameRateCompatibility::Exact;
+    return layerVotedWithDefaultCompatibility || layerVotedWithNoVote || layerVotedWithCategory ||
             layerVotedWithExactCompatibility || childrenHaveFrameRate;
 }
 
@@ -1306,7 +1312,7 @@
     }();
 
     bool transactionNeeded = false;
-    root->propagateFrameRateForLayerTree({}, &transactionNeeded);
+    root->propagateFrameRateForLayerTree({}, false, &transactionNeeded);
 
     // TODO(b/195668952): we probably don't need eTraversalNeeded here
     if (transactionNeeded) {
@@ -1314,13 +1320,13 @@
     }
 }
 
-bool Layer::setFrameRate(FrameRate frameRate) {
-    if (mDrawingState.frameRate == frameRate) {
+bool Layer::setFrameRate(FrameRate::FrameRateVote frameRateVote) {
+    if (mDrawingState.frameRate.vote == frameRateVote) {
         return false;
     }
 
     mDrawingState.sequence++;
-    mDrawingState.frameRate = frameRate;
+    mDrawingState.frameRate.vote = frameRateVote;
     mDrawingState.modified = true;
 
     updateTreeHasFrameRateVote();
@@ -1329,6 +1335,34 @@
     return true;
 }
 
+bool Layer::setFrameRateCategory(FrameRateCategory category, bool smoothSwitchOnly) {
+    if (mDrawingState.frameRate.category == category &&
+        mDrawingState.frameRate.categorySmoothSwitchOnly == smoothSwitchOnly) {
+        return false;
+    }
+
+    mDrawingState.sequence++;
+    mDrawingState.frameRate.category = category;
+    mDrawingState.frameRate.categorySmoothSwitchOnly = smoothSwitchOnly;
+    mDrawingState.modified = true;
+
+    updateTreeHasFrameRateVote();
+
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
+bool Layer::setFrameRateSelectionStrategy(FrameRateSelectionStrategy strategy) {
+    if (mDrawingState.frameRateSelectionStrategy == strategy) return false;
+    mDrawingState.frameRateSelectionStrategy = strategy;
+    mDrawingState.sequence++;
+    mDrawingState.modified = true;
+
+    updateTreeHasFrameRateVote();
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info,
                                                       nsecs_t postTime) {
     mDrawingState.postTime = postTime;
@@ -1463,7 +1497,7 @@
     addSurfaceFrameDroppedForBuffer(surfaceFrame, postTime);
 }
 
-bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate) {
+bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate, nsecs_t now) {
     if (mDrawingState.frameRateForLayerTree == frameRate) {
         return false;
     }
@@ -1477,19 +1511,20 @@
     setTransactionFlags(eTransactionNeeded);
 
     mFlinger->mScheduler
-            ->recordLayerHistory(sequence, getLayerProps(), systemTime(),
+            ->recordLayerHistory(sequence, getLayerProps(), now, now,
                                  scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
     return true;
 }
 
-bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps) {
+bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps,
+                                     nsecs_t now) {
     if (mDrawingState.frameRateForLayerTree == frameRate) {
         return false;
     }
 
     mDrawingState.frameRateForLayerTree = frameRate;
     mFlinger->mScheduler
-            ->recordLayerHistory(sequence, layerProps, systemTime(),
+            ->recordLayerHistory(sequence, layerProps, now, now,
                                  scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
     return true;
 }
@@ -1610,7 +1645,7 @@
     result.append("\n");
 }
 
-void Layer::miniDump(std::string& result, const DisplayDevice& display) const {
+void Layer::miniDumpLegacy(std::string& result, const DisplayDevice& display) const {
     const auto outputLayer = findOutputLayerForDisplay(&display);
     if (!outputLayer) {
         return;
@@ -1646,10 +1681,10 @@
     StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
                   crop.bottom);
     const auto frameRate = getFrameRateForLayerTree();
-    if (frameRate.rate.isValid() || frameRate.type != FrameRateCompatibility::Default) {
-        StringAppendF(&result, "%s %15s %17s", to_string(frameRate.rate).c_str(),
-                      ftl::enum_string(frameRate.type).c_str(),
-                      ftl::enum_string(frameRate.seamlessness).c_str());
+    if (frameRate.vote.rate.isValid() || frameRate.vote.type != FrameRateCompatibility::Default) {
+        StringAppendF(&result, "%s %15s %17s", to_string(frameRate.vote.rate).c_str(),
+                      ftl::enum_string(frameRate.vote.type).c_str(),
+                      ftl::enum_string(frameRate.vote.seamlessness).c_str());
     } else {
         result.append(41, ' ');
     }
@@ -1661,6 +1696,41 @@
     result.append("\n");
 }
 
+void Layer::miniDump(std::string& result, const frontend::LayerSnapshot& snapshot,
+                     const DisplayDevice& display) const {
+    const auto outputLayer = findOutputLayerForDisplay(&display, snapshot.path);
+    if (!outputLayer) {
+        return;
+    }
+
+    StringAppendF(&result, " %s\n", snapshot.debugName.c_str());
+    StringAppendF(&result, "  %10zu | ", snapshot.globalZ);
+    StringAppendF(&result, "  %10d | ",
+                  snapshot.layerMetadata.getInt32(gui::METADATA_WINDOW_TYPE, 0));
+    StringAppendF(&result, "%10s | ", toString(getCompositionType(outputLayer)).c_str());
+    const auto& outputLayerState = outputLayer->getState();
+    StringAppendF(&result, "%10s | ", toString(outputLayerState.bufferTransform).c_str());
+    const Rect& frame = outputLayerState.displayFrame;
+    StringAppendF(&result, "%4d %4d %4d %4d | ", frame.left, frame.top, frame.right, frame.bottom);
+    const FloatRect& crop = outputLayerState.sourceCrop;
+    StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
+                  crop.bottom);
+    const auto frameRate = snapshot.frameRate;
+    if (frameRate.vote.rate.isValid() || frameRate.vote.type != FrameRateCompatibility::Default) {
+        StringAppendF(&result, "%s %15s %17s", to_string(frameRate.vote.rate).c_str(),
+                      ftl::enum_string(frameRate.vote.type).c_str(),
+                      ftl::enum_string(frameRate.vote.seamlessness).c_str());
+    } else {
+        result.append(41, ' ');
+    }
+
+    const auto focused = isLayerFocusedBasedOnPriority(snapshot.frameRateSelectionPriority);
+    StringAppendF(&result, "    [%s]\n", focused ? "*" : " ");
+
+    result.append(kDumpTableRowLength, '-');
+    result.append("\n");
+}
+
 void Layer::dumpFrameStats(std::string& result) const {
     mFrameTracker.dumpStats(result);
 }
@@ -2070,6 +2140,13 @@
 }
 
 RoundedCornerState Layer::getRoundedCornerState() const {
+    // Today's DPUs cannot do rounded corners. If RenderEngine cannot render
+    // protected content, remove rounded corners from protected content so it
+    // can be rendered by the DPU.
+    if (isProtected() && !mFlinger->getRenderEngine().supportsProtectedContent()) {
+        return {};
+    }
+
     // Get parent settings
     RoundedCornerState parentSettings;
     const auto& parent = mDrawingParent.promote();
@@ -2152,8 +2229,9 @@
     setTransactionFlags(eTransactionNeeded);
 }
 
-LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) {
-    LayerProto* layerProto = layersProto.add_layers();
+perfetto::protos::LayerProto* Layer::writeToProto(perfetto::protos::LayersProto& layersProto,
+                                                  uint32_t traceFlags) {
+    perfetto::protos::LayerProto* layerProto = layersProto.add_layers();
     writeToProtoDrawingState(layerProto);
     writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags);
 
@@ -2170,20 +2248,22 @@
     return layerProto;
 }
 
-void Layer::writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack) {
+void Layer::writeCompositionStateToProto(perfetto::protos::LayerProto* layerProto,
+                                         ui::LayerStack layerStack) {
     ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread.
     ftl::FakeGuard mainThreadGuard(kMainThreadContext);
 
     // Only populate for the primary display.
     if (const auto display = mFlinger->getDisplayFromLayerStack(layerStack)) {
         const auto compositionType = getCompositionType(*display);
-        layerProto->set_hwc_composition_type(static_cast<HwcCompositionType>(compositionType));
+        layerProto->set_hwc_composition_type(
+                static_cast<perfetto::protos::HwcCompositionType>(compositionType));
         LayerProtoHelper::writeToProto(getVisibleRegion(display),
                                        [&]() { return layerProto->mutable_visible_region(); });
     }
 }
 
-void Layer::writeToProtoDrawingState(LayerProto* layerInfo) {
+void Layer::writeToProtoDrawingState(perfetto::protos::LayerProto* layerInfo) {
     const ui::Transform transform = getTransform();
     auto buffer = getExternalTexture();
     if (buffer != nullptr) {
@@ -2222,8 +2302,8 @@
     layerInfo->set_shadow_radius(mEffectiveShadowRadius);
 }
 
-void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet stateSet,
-                                    uint32_t traceFlags) {
+void Layer::writeToProtoCommonState(perfetto::protos::LayerProto* layerInfo,
+                                    LayerVector::StateSet stateSet, uint32_t traceFlags) {
     const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
     const LayerVector& children = useDrawing ? mDrawingChildren : mCurrentChildren;
     const State& state = useDrawing ? mDrawingState : mDrawingState;
@@ -2271,15 +2351,11 @@
     auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
     if (parent != nullptr) {
         layerInfo->set_parent(parent->sequence);
-    } else {
-        layerInfo->set_parent(-1);
     }
 
     auto zOrderRelativeOf = state.zOrderRelativeOf.promote();
     if (zOrderRelativeOf != nullptr) {
         layerInfo->set_z_order_relative_of(zOrderRelativeOf->sequence);
-    } else {
-        layerInfo->set_z_order_relative_of(-1);
     }
 
     layerInfo->set_is_relative_of(state.isRelativeOf);
@@ -2351,11 +2427,7 @@
         info.touchableRegion.clear();
     }
 
-    const Rect roundedFrameInDisplay = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
-    info.frameLeft = roundedFrameInDisplay.left;
-    info.frameTop = roundedFrameInDisplay.top;
-    info.frameRight = roundedFrameInDisplay.right;
-    info.frameBottom = roundedFrameInDisplay.bottom;
+    info.frame = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
 
     ui::Transform inputToLayer;
     inputToLayer.set(inputBounds.left, inputBounds.top);
@@ -2577,6 +2649,24 @@
     return display->getCompositionDisplay()->getOutputLayerForLayer(layerFE);
 }
 
+compositionengine::OutputLayer* Layer::findOutputLayerForDisplay(
+        const DisplayDevice* display, const frontend::LayerHierarchy::TraversalPath& path) const {
+    if (!display) return nullptr;
+    if (!mFlinger->mLayerLifecycleManagerEnabled) {
+        return display->getCompositionDisplay()->getOutputLayerForLayer(
+                getCompositionEngineLayerFE());
+    }
+    sp<LayerFE> layerFE;
+    for (auto& [p, layer] : mLayerFEs) {
+        if (p == path) {
+            layerFE = layer;
+        }
+    }
+
+    if (!layerFE) return nullptr;
+    return display->getCompositionDisplay()->getOutputLayerForLayer(layerFE);
+}
+
 Region Layer::getVisibleRegion(const DisplayDevice* display) const {
     const auto outputLayer = findOutputLayerForDisplay(display);
     return outputLayer ? outputLayer->getState().visibleRegion : Region();
@@ -2584,7 +2674,7 @@
 
 void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
     mSnapshot->path.id = clonedFrom->getSequence();
-    mSnapshot->path.mirrorRootId = mirrorRootId;
+    mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
 
     cloneDrawingState(clonedFrom.get());
     mClonedFrom = clonedFrom;
@@ -2758,36 +2848,6 @@
     layer->mDrawingParent = sp<Layer>::fromExisting(this);
 }
 
-Layer::FrameRateCompatibility Layer::FrameRate::convertCompatibility(int8_t compatibility) {
-    switch (compatibility) {
-        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT:
-            return FrameRateCompatibility::Default;
-        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
-            return FrameRateCompatibility::ExactOrMultiple;
-        case ANATIVEWINDOW_FRAME_RATE_EXACT:
-            return FrameRateCompatibility::Exact;
-        case ANATIVEWINDOW_FRAME_RATE_MIN:
-            return FrameRateCompatibility::Min;
-        case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
-            return FrameRateCompatibility::NoVote;
-        default:
-            LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
-            return FrameRateCompatibility::Default;
-    }
-}
-
-scheduler::Seamlessness Layer::FrameRate::convertChangeFrameRateStrategy(int8_t strategy) {
-    switch (strategy) {
-        case ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
-            return Seamlessness::OnlySeamless;
-        case ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS:
-            return Seamlessness::SeamedAndSeamless;
-        default:
-            LOG_ALWAYS_FATAL("Invalid change frame sate strategy value %d", strategy);
-            return Seamlessness::Default;
-    }
-}
-
 bool Layer::isInternalDisplayOverlay() const {
     const State& s(mDrawingState);
     if (s.flags & layer_state_t::eLayerSkipScreenshot) {
@@ -2872,17 +2932,14 @@
         }
     }
 
-    // Prevent tracing the same release multiple times.
-    if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) {
-        mPreviousReleasedFrameNumber = mPreviousFrameNumber;
-    }
-
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
     }
-    mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    if (mBufferInfo.mBuffer) {
+        mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    }
 }
 
 void Layer::onSurfaceFrameCreated(
@@ -3058,6 +3115,32 @@
     return true;
 }
 
+void Layer::releasePreviousBuffer() {
+    mReleasePreviousBuffer = true;
+    if (!mBufferInfo.mBuffer ||
+        (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) ||
+         mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) {
+        // If mDrawingState has a buffer, and we are about to update again
+        // before swapping to drawing state, then the first buffer will be
+        // dropped and we should decrement the pending buffer count and
+        // call any release buffer callbacks if set.
+        callReleaseBufferCallback(mDrawingState.releaseBufferListener,
+                                  mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
+                                  mDrawingState.acquireFence);
+        decrementPendingBufferCount();
+        if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
+            mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
+            addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX, systemTime());
+            mDrawingState.bufferSurfaceFrameTX.reset();
+        }
+    } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
+        callReleaseBufferCallback(mDrawingState.releaseBufferListener,
+                                  mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
+                                  mLastClientCompositionFence);
+        mLastClientCompositionFence = nullptr;
+    }
+}
+
 void Layer::resetDrawingStateBufferInfo() {
     mDrawingState.producerId = 0;
     mDrawingState.frameNumber = 0;
@@ -3082,29 +3165,7 @@
     ATRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber);
 
     if (mDrawingState.buffer) {
-        mReleasePreviousBuffer = true;
-        if (!mBufferInfo.mBuffer ||
-            (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) ||
-             mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) {
-            // If mDrawingState has a buffer, and we are about to update again
-            // before swapping to drawing state, then the first buffer will be
-            // dropped and we should decrement the pending buffer count and
-            // call any release buffer callbacks if set.
-            callReleaseBufferCallback(mDrawingState.releaseBufferListener,
-                                      mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mDrawingState.acquireFence);
-            decrementPendingBufferCount();
-            if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
-                mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
-                addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX, systemTime());
-                mDrawingState.bufferSurfaceFrameTX.reset();
-            }
-        } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
-            callReleaseBufferCallback(mDrawingState.releaseBufferListener,
-                                      mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mLastClientCompositionFence);
-            mLastClientCompositionFence = nullptr;
-        }
+        releasePreviousBuffer();
     } else if (buffer) {
         // if we are latching a buffer for the first time then clear the mLastLatchTime since
         // we don't want to incorrectly classify a frame if we miss the desired present time.
@@ -3122,6 +3183,11 @@
         mDrawingState.bufferSurfaceFrameTX = nullptr;
         setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
         return true;
+    } else {
+        // release sideband stream if it exists and a non null buffer is being set
+        if (mDrawingState.sidebandStream != nullptr) {
+            setSidebandStream(nullptr, info, postTime);
+        }
     }
 
     if ((mDrawingState.producerId > bufferData.producerId) ||
@@ -3162,7 +3228,7 @@
                                       mOwnerUid, postTime, getGameMode());
 
     if (mFlinger->mLegacyFrontEndEnabled) {
-        recordLayerHistoryBufferUpdate(getLayerProps());
+        recordLayerHistoryBufferUpdate(getLayerProps(), systemTime());
     }
 
     setFrameTimelineVsyncForBufferTransaction(info, postTime);
@@ -3193,7 +3259,7 @@
     mDrawingState.isAutoTimestamp = isAutoTimestamp;
 }
 
-void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) {
+void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps, nsecs_t now) {
     ATRACE_CALL();
     const nsecs_t presentTime = [&] {
         if (!mDrawingState.isAutoTimestamp) {
@@ -3247,14 +3313,14 @@
         ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
     }
 
-    mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime,
+    mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, now,
                                              scheduler::LayerHistory::LayerUpdateType::Buffer);
 }
 
-void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProps) {
+void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProps, nsecs_t now) {
     const nsecs_t presentTime =
             mDrawingState.isAutoTimestamp ? 0 : mDrawingState.desiredPresentTime;
-    mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime,
+    mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, now,
                                              scheduler::LayerHistory::LayerUpdateType::AnimationTX);
 }
 
@@ -3298,7 +3364,7 @@
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
-    setIsSmallDirty();
+    setIsSmallDirty(surfaceDamage, getTransform());
     return true;
 }
 
@@ -3310,7 +3376,8 @@
     return true;
 }
 
-bool Layer::setSidebandStream(const sp<NativeHandle>& sidebandStream) {
+bool Layer::setSidebandStream(const sp<NativeHandle>& sidebandStream, const FrameTimelineInfo& info,
+                              nsecs_t postTime) {
     if (mDrawingState.sidebandStream == sidebandStream) return false;
 
     if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) {
@@ -3321,6 +3388,12 @@
 
     mDrawingState.sidebandStream = sidebandStream;
     mDrawingState.modified = true;
+    if (sidebandStream != nullptr && mDrawingState.buffer != nullptr) {
+        releasePreviousBuffer();
+        resetDrawingStateBufferInfo();
+        mDrawingState.bufferSurfaceFrameTX = nullptr;
+        setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
+    }
     setTransactionFlags(eTransactionNeeded);
     if (!mSidebandStreamChanged.exchange(true)) {
         // mSidebandStreamChanged was false
@@ -3595,8 +3668,8 @@
 }
 
 sp<Layer> Layer::createClone(uint32_t mirrorRootId) {
-    LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
-    args.textureName = mTextureName;
+    surfaceflinger::LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0,
+                                           LayerMetadata());
     sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
     layer->setInitialValuesForClone(sp<Layer>::fromExisting(this), mirrorRootId);
     return layer;
@@ -3859,13 +3932,6 @@
     return true;
 }
 
-bool Layer::isHdrY410() const {
-    // pixel format is HDR Y410 masquerading as RGBA_1010102
-    return (mBufferInfo.mDataspace == ui::Dataspace::BT2020_ITU_PQ &&
-            mBufferInfo.mApi == NATIVE_WINDOW_API_MEDIA &&
-            mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102);
-}
-
 sp<LayerFE> Layer::getCompositionEngineLayerFE() const {
     // There's no need to get a CE Layer if the layer isn't going to draw anything.
     return hasSomethingToDraw() ? mLegacyLayerFE : nullptr;
@@ -4007,7 +4073,7 @@
         const std::optional<Fps> renderRate =
                 mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
 
-        const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate);
+        const auto vote = frameRateToSetFrameRateVotePayload(getFrameRateForLayerTree());
         const auto gameMode = getGameMode();
 
         if (presentFence->isValid()) {
@@ -4267,7 +4333,6 @@
     snapshot->contentOpaque = isOpaque(mDrawingState);
     snapshot->layerOpaqueFlagSet =
             (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
-    snapshot->isHdrY410 = isHdrY410();
     sp<Layer> p = mDrawingParent.promote();
     if (p != nullptr) {
         snapshot->parentTransform = p->getTransform();
@@ -4346,7 +4411,9 @@
     mLastLatchTime = latchTime;
 }
 
-void Layer::setIsSmallDirty() {
+void Layer::setIsSmallDirty(const Region& damageRegion,
+                            const ui::Transform& layerToDisplayTransform) {
+    mSmallDirty = false;
     if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
         return;
     }
@@ -4355,22 +4422,24 @@
         mWindowType != WindowInfo::Type::BASE_APPLICATION) {
         return;
     }
-    Rect bounds = mDrawingState.surfaceDamageRegion.getBounds();
+
+    Rect bounds = damageRegion.getBounds();
     if (!bounds.isValid()) {
         return;
     }
 
+    // Transform to screen space.
+    bounds = layerToDisplayTransform.transform(bounds);
+
     // If the damage region is a small dirty, this could give the hint for the layer history that
     // it could suppress the heuristic rate when calculating.
-    mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerUid,
+    mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerAppId,
                                                          bounds.getWidth() * bounds.getHeight());
 }
 
-// ---------------------------------------------------------------------------
-
-std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
-    return stream << "{rate=" << rate.rate << " type=" << ftl::enum_string(rate.type)
-                  << " seamlessness=" << ftl::enum_string(rate.seamlessness) << '}';
+void Layer::setIsSmallDirty(frontend::LayerSnapshot* snapshot) {
+    setIsSmallDirty(snapshot->surfaceDamage, snapshot->localTransform);
+    snapshot->isSmallDirty = mSmallDirty;
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 895d25a..dd91adc 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -23,8 +23,6 @@
 #include <gui/WindowInfo.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/vec4.h>
-#include <renderengine/Mesh.h>
-#include <renderengine/Texture.h>
 #include <sys/types.h>
 #include <ui/BlurRegion.h>
 #include <ui/FloatRect.h>
@@ -113,7 +111,8 @@
     };
 
     using FrameRate = scheduler::LayerInfo::FrameRate;
-    using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
+    using FrameRateCompatibility = scheduler::FrameRateCompatibility;
+    using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
 
     struct State {
         int32_t z;
@@ -190,6 +189,8 @@
         // The combined frame rate of parents / children of this layer
         FrameRate frameRateForLayerTree;
 
+        FrameRateSelectionStrategy frameRateSelectionStrategy;
+
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
         // the graphic producers should receive a transform hint as if the
@@ -237,7 +238,7 @@
         bool useVsyncIdForRefreshRateSelection = false;
     };
 
-    explicit Layer(const LayerCreationArgs& args);
+    explicit Layer(const surfaceflinger::LayerCreationArgs& args);
     virtual ~Layer();
 
     static bool isLayerFocusedBasedOnPriority(int32_t priority);
@@ -319,7 +320,8 @@
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
     bool setApi(int32_t /*api*/);
-    bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/);
+    bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/,
+                           const FrameTimelineInfo& /* info*/, nsecs_t /* postTime */);
     bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/,
                                           bool willPresent);
     virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace)
@@ -343,6 +345,7 @@
     virtual sp<LayerFE> getCompositionEngineLayerFE() const;
     virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
     sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
+    sp<LayerFE> getOrCreateCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
 
     const frontend::LayerSnapshot* getLayerSnapshot() const;
     frontend::LayerSnapshot* editLayerSnapshot();
@@ -426,12 +429,10 @@
     bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const;
 
     // from graphics API
-    ui::Dataspace translateDataspace(ui::Dataspace dataspace);
+    static ui::Dataspace translateDataspace(ui::Dataspace dataspace);
     void updateCloneBufferInfo();
     uint64_t mPreviousFrameNumber = 0;
 
-    bool isHdrY410() const;
-
     /*
      * called after composition.
      * returns true if the layer latched a new buffer this frame.
@@ -637,17 +638,19 @@
 
     bool isRemovedFromCurrentState() const;
 
-    LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags);
-    void writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack);
+    perfetto::protos::LayerProto* writeToProto(perfetto::protos::LayersProto& layersProto,
+                                               uint32_t traceFlags);
+    void writeCompositionStateToProto(perfetto::protos::LayerProto* layerProto,
+                                      ui::LayerStack layerStack);
 
     // Write states that are modified by the main thread. This includes drawing
     // state as well as buffer data. This should be called in the main or tracing
     // thread.
-    void writeToProtoDrawingState(LayerProto* layerInfo);
+    void writeToProtoDrawingState(perfetto::protos::LayerProto* layerInfo);
     // Write drawing or current state. If writing current state, the caller should hold the
     // external mStateLock. If writing drawing state, this function should be called on the
     // main or tracing thread.
-    void writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet,
+    void writeToProtoCommonState(perfetto::protos::LayerProto* layerInfo, LayerVector::StateSet,
                                  uint32_t traceFlags = LayerTracing::TRACE_ALL);
 
     gui::WindowInfo::Type getWindowType() const { return mWindowType; }
@@ -692,7 +695,8 @@
 
     gui::LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const;
 
-    void miniDump(std::string& result, const DisplayDevice&) const;
+    void miniDumpLegacy(std::string& result, const DisplayDevice&) const;
+    void miniDump(std::string& result, const frontend::LayerSnapshot&, const DisplayDevice&) const;
     void dumpFrameStats(std::string& result) const;
     void dumpOffscreenDebugInfo(std::string& result) const;
     void clearFrameStats();
@@ -778,7 +782,10 @@
      */
     Rect getCroppedBufferSize(const Layer::State& s) const;
 
-    bool setFrameRate(FrameRate);
+    bool setFrameRate(FrameRate::FrameRateVote);
+    bool setFrameRateCategory(FrameRateCategory, bool smoothSwitchOnly);
+
+    bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
 
     virtual void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
     void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime);
@@ -830,6 +837,8 @@
 
     pid_t getOwnerPid() { return mOwnerPid; }
 
+    int32_t getOwnerAppId() { return mOwnerAppId; }
+
     // This layer is not a clone, but it's the parent to the cloned hierarchy. The
     // variable mClonedChild represents the top layer that will be cloned so this
     // layer will be the parent of mClonedChild.
@@ -901,10 +910,10 @@
     void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                    const sp<GraphicBuffer>& buffer, uint64_t framenumber,
                                    const sp<Fence>& releaseFence);
-    bool setFrameRateForLayerTreeLegacy(FrameRate);
-    bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&);
-    void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&);
-    void recordLayerHistoryAnimationTx(const scheduler::LayerProps&);
+    bool setFrameRateForLayerTreeLegacy(FrameRate, nsecs_t now);
+    bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&, nsecs_t now);
+    void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&, nsecs_t now);
+    void recordLayerHistoryAnimationTx(const scheduler::LayerProps&, nsecs_t now);
     auto getLayerProps() const {
         return scheduler::LayerProps{
                 .visible = isVisible(),
@@ -919,6 +928,7 @@
     void setTransformHint(std::optional<ui::Transform::RotationFlags> transformHint) {
         mTransformHint = transformHint;
     }
+    void commitTransaction();
     // Keeps track of the previously presented layer stacks. This is used to get
     // the release fences from the correct displays when we release the last buffer
     // from the layer.
@@ -927,7 +937,8 @@
     const sp<SurfaceFlinger> mFlinger;
 
     // Check if the damage region is a small dirty.
-    void setIsSmallDirty();
+    void setIsSmallDirty(const Region& damageRegion, const ui::Transform& layerToDisplayTransform);
+    void setIsSmallDirty(frontend::LayerSnapshot* snapshot);
 
 protected:
     // For unit tests
@@ -942,7 +953,6 @@
     void preparePerFrameCompositionState();
     void preparePerFrameBufferCompositionState();
     void preparePerFrameEffectsCompositionState();
-    virtual void commitTransaction(State& stateToCommit);
     void gatherBufferInfo();
     void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
 
@@ -972,6 +982,8 @@
     void addZOrderRelative(const wp<Layer>& relative);
     void removeZOrderRelative(const wp<Layer>& relative);
     compositionengine::OutputLayer* findOutputLayerForDisplay(const DisplayDevice*) const;
+    compositionengine::OutputLayer* findOutputLayerForDisplay(
+            const DisplayDevice*, const frontend::LayerHierarchy::TraversalPath& path) const;
     bool usingRelativeZ(LayerVector::StateSet) const;
 
     virtual ui::Transform getInputTransform() const;
@@ -1053,6 +1065,8 @@
     // If created from a system process, the value can be passed in.
     pid_t mOwnerPid;
 
+    int32_t mOwnerAppId;
+
     // Keeps track of the time SF latched the last buffer from this layer.
     // Used in buffer stuffing analysis in FrameTimeline.
     nsecs_t mLastLatchTime = 0;
@@ -1062,6 +1076,10 @@
     sp<Fence> mLastClientCompositionFence;
     bool mClearClientCompositionFenceOnLayerDisplayed = false;
 private:
+    // Range of uids allocated for a user.
+    // This value is taken from android.os.UserHandle#PER_USER_RANGE.
+    static constexpr int32_t PER_USER_RANGE = 100000;
+
     friend class SlotGenerationTest;
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
@@ -1076,7 +1094,8 @@
 
     aidl::android::hardware::graphics::composer3::Composition getCompositionType(
             const DisplayDevice&) const;
-
+    aidl::android::hardware::graphics::composer3::Composition getCompositionType(
+            const compositionengine::OutputLayer*) const;
     /**
      * Returns an unsorted vector of all layers that are part of this tree.
      * That includes the current layer and all its descendants.
@@ -1092,7 +1111,8 @@
                                           const std::vector<Layer*>& layersInTree);
 
     void updateTreeHasFrameRateVote();
-    bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded);
+    bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+                                        bool* transactionNeeded);
     void setZOrderRelativeOf(const wp<Layer>& relativeOf);
     bool isTrustedOverlay() const;
     gui::DropInputMode getDropInputMode() const;
@@ -1154,6 +1174,11 @@
     void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
                                    std::vector<JankData>& jankData);
 
+    bool shouldOverrideChildrenFrameRate() const {
+        return getDrawingState().frameRateSelectionStrategy ==
+                FrameRateSelectionStrategy::OverrideChildren;
+    }
+
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
     // a transform from the current layer coordinate space to display(screen) coordinate space.
@@ -1200,10 +1225,9 @@
     half4 mBorderColor;
 
     void setTransformHintLegacy(ui::Transform::RotationFlags);
+    void releasePreviousBuffer();
     void resetDrawingStateBufferInfo();
 
-    const uint32_t mTextureName;
-
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
     ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0;
@@ -1212,9 +1236,6 @@
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     sp<IBinder> mPreviousReleaseBufferEndpoint;
-    uint64_t mPreviousReleasedFrameNumber = 0;
-
-    uint64_t mPreviousBarrierFrameNumber = 0;
 
     bool mReleasePreviousBuffer = false;
 
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index f855f27..f25619a 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -16,7 +16,7 @@
 
 // #define LOG_NDEBUG 0
 #undef LOG_TAG
-#define LOG_TAG "LayerFE"
+#define LOG_TAG "SurfaceFlinger"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <gui/GLConsumer.h>
@@ -223,9 +223,7 @@
     layerSettings.source.buffer.buffer = mSnapshot->externalTexture;
     layerSettings.source.buffer.isOpaque = mSnapshot->contentOpaque;
     layerSettings.source.buffer.fence = mSnapshot->acquireFence;
-    layerSettings.source.buffer.textureName = mSnapshot->textureName;
     layerSettings.source.buffer.usePremultipliedAlpha = mSnapshot->premultipliedAlpha;
-    layerSettings.source.buffer.isY410BT2020 = mSnapshot->isHdrY410;
     bool hasSmpte2086 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086;
     bool hasCta861_3 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3;
     float maxLuminance = 0.f;
@@ -249,10 +247,13 @@
     layerSettings.frameNumber = mSnapshot->frameNumber;
     layerSettings.bufferId = mSnapshot->externalTexture->getId();
 
+    const bool useFiltering = targetSettings.needsFiltering ||
+                              mSnapshot->geomLayerTransform.needsBilinearFiltering();
+
     // Query the texture matrix given our current filtering mode.
     float textureMatrix[16];
     getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
-                              mSnapshot->geomBufferTransform, targetSettings.needsFiltering,
+                              mSnapshot->geomBufferTransform, useFiltering,
                               textureMatrix);
 
     if (mSnapshot->geomBufferUsesDisplayInverseTransform) {
@@ -303,7 +304,7 @@
             mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
             mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));
 
-    layerSettings.source.buffer.useTextureFiltering = targetSettings.needsFiltering;
+    layerSettings.source.buffer.useTextureFiltering = useFiltering;
     layerSettings.source.buffer.textureTransform =
             mat4(static_cast<const float*>(textureMatrix)) * tr;
 
@@ -312,7 +313,7 @@
 
 void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster,
                                              const Rect& layerStackRect) const {
-    renderengine::ShadowSettings state = mSnapshot->shadowSettings;
+    ShadowSettings state = mSnapshot->shadowSettings;
     if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) {
         return;
     }
@@ -347,7 +348,7 @@
 }
 
 int32_t LayerFE::getSequence() const {
-    return mSnapshot->sequence;
+    return static_cast<int32_t>(mSnapshot->uniqueSequence);
 }
 
 bool LayerFE::hasRoundedCorners() const {
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 1c7581b..aa6026e 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -29,28 +29,30 @@
 
 namespace surfaceflinger {
 
-void LayerProtoHelper::writePositionToProto(const float x, const float y,
-                                            std::function<PositionProto*()> getPositionProto) {
+void LayerProtoHelper::writePositionToProto(
+        const float x, const float y,
+        std::function<perfetto::protos::PositionProto*()> getPositionProto) {
     if (x != 0 || y != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
-        PositionProto* position = getPositionProto();
+        perfetto::protos::PositionProto* position = getPositionProto();
         position->set_x(x);
         position->set_y(y);
     }
 }
 
-void LayerProtoHelper::writeSizeToProto(const uint32_t w, const uint32_t h,
-                                        std::function<SizeProto*()> getSizeProto) {
+void LayerProtoHelper::writeSizeToProto(
+        const uint32_t w, const uint32_t h,
+        std::function<perfetto::protos::SizeProto*()> getSizeProto) {
     if (w != 0 || h != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
-        SizeProto* size = getSizeProto();
+        perfetto::protos::SizeProto* size = getSizeProto();
         size->set_w(w);
         size->set_h(h);
     }
 }
 
-void LayerProtoHelper::writeToProto(const Region& region,
-                                    std::function<RegionProto*()> getRegionProto) {
+void LayerProtoHelper::writeToProto(
+        const Region& region, std::function<perfetto::protos::RegionProto*()> getRegionProto) {
     if (region.isEmpty()) {
         return;
     }
@@ -58,7 +60,8 @@
     writeToProto(region, getRegionProto());
 }
 
-void LayerProtoHelper::writeToProto(const Region& region, RegionProto* regionProto) {
+void LayerProtoHelper::writeToProto(const Region& region,
+                                    perfetto::protos::RegionProto* regionProto) {
     if (region.isEmpty()) {
         return;
     }
@@ -72,7 +75,8 @@
     }
 }
 
-void LayerProtoHelper::readFromProto(const RegionProto& regionProto, Region& outRegion) {
+void LayerProtoHelper::readFromProto(const perfetto::protos::RegionProto& regionProto,
+                                     Region& outRegion) {
     for (int i = 0; i < regionProto.rect_size(); i++) {
         Rect rect;
         readFromProto(regionProto.rect(i), rect);
@@ -80,32 +84,34 @@
     }
 }
 
-void LayerProtoHelper::writeToProto(const Rect& rect, std::function<RectProto*()> getRectProto) {
+void LayerProtoHelper::writeToProto(const Rect& rect,
+                                    std::function<perfetto::protos::RectProto*()> getRectProto) {
     if (rect.left != 0 || rect.right != 0 || rect.top != 0 || rect.bottom != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
         writeToProto(rect, getRectProto());
     }
 }
 
-void LayerProtoHelper::writeToProto(const Rect& rect, RectProto* rectProto) {
+void LayerProtoHelper::writeToProto(const Rect& rect, perfetto::protos::RectProto* rectProto) {
     rectProto->set_left(rect.left);
     rectProto->set_top(rect.top);
     rectProto->set_bottom(rect.bottom);
     rectProto->set_right(rect.right);
 }
 
-void LayerProtoHelper::readFromProto(const RectProto& proto, Rect& outRect) {
+void LayerProtoHelper::readFromProto(const perfetto::protos::RectProto& proto, Rect& outRect) {
     outRect.left = proto.left();
     outRect.top = proto.top();
     outRect.bottom = proto.bottom();
     outRect.right = proto.right();
 }
 
-void LayerProtoHelper::writeToProto(const FloatRect& rect,
-                                    std::function<FloatRectProto*()> getFloatRectProto) {
+void LayerProtoHelper::writeToProto(
+        const FloatRect& rect,
+        std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto) {
     if (rect.left != 0 || rect.right != 0 || rect.top != 0 || rect.bottom != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
-        FloatRectProto* rectProto = getFloatRectProto();
+        perfetto::protos::FloatRectProto* rectProto = getFloatRectProto();
         rectProto->set_left(rect.left);
         rectProto->set_top(rect.top);
         rectProto->set_bottom(rect.bottom);
@@ -113,10 +119,11 @@
     }
 }
 
-void LayerProtoHelper::writeToProto(const half4 color, std::function<ColorProto*()> getColorProto) {
+void LayerProtoHelper::writeToProto(const half4 color,
+                                    std::function<perfetto::protos::ColorProto*()> getColorProto) {
     if (color.r != 0 || color.g != 0 || color.b != 0 || color.a != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
-        ColorProto* colorProto = getColorProto();
+        perfetto::protos::ColorProto* colorProto = getColorProto();
         colorProto->set_r(color.r);
         colorProto->set_g(color.g);
         colorProto->set_b(color.b);
@@ -125,7 +132,7 @@
 }
 
 void LayerProtoHelper::writeToProtoDeprecated(const ui::Transform& transform,
-                                              TransformProto* transformProto) {
+                                              perfetto::protos::TransformProto* transformProto) {
     const uint32_t type = transform.getType() | (transform.getOrientation() << 8);
     transformProto->set_type(type);
 
@@ -141,7 +148,7 @@
 }
 
 void LayerProtoHelper::writeTransformToProto(const ui::Transform& transform,
-                                             TransformProto* transformProto) {
+                                             perfetto::protos::TransformProto* transformProto) {
     const uint32_t type = transform.getType() | (transform.getOrientation() << 8);
     transformProto->set_type(type);
 
@@ -156,12 +163,13 @@
     }
 }
 
-void LayerProtoHelper::writeToProto(const renderengine::ExternalTexture& buffer,
-                                    std::function<ActiveBufferProto*()> getActiveBufferProto) {
+void LayerProtoHelper::writeToProto(
+        const renderengine::ExternalTexture& buffer,
+        std::function<perfetto::protos::ActiveBufferProto*()> getActiveBufferProto) {
     if (buffer.getWidth() != 0 || buffer.getHeight() != 0 || buffer.getUsage() != 0 ||
         buffer.getPixelFormat() != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
-        ActiveBufferProto* activeBufferProto = getActiveBufferProto();
+        auto* activeBufferProto = getActiveBufferProto();
         activeBufferProto->set_width(buffer.getWidth());
         activeBufferProto->set_height(buffer.getHeight());
         activeBufferProto->set_stride(buffer.getUsage());
@@ -171,12 +179,12 @@
 
 void LayerProtoHelper::writeToProto(
         const WindowInfo& inputInfo, const wp<Layer>& touchableRegionBounds,
-        std::function<InputWindowInfoProto*()> getInputWindowInfoProto) {
+        std::function<perfetto::protos::InputWindowInfoProto*()> getInputWindowInfoProto) {
     if (inputInfo.token == nullptr) {
         return;
     }
 
-    InputWindowInfoProto* proto = getInputWindowInfoProto();
+    perfetto::protos::InputWindowInfoProto* proto = getInputWindowInfoProto();
     proto->set_layout_params_flags(inputInfo.layoutParamsFlags.get());
     proto->set_input_config(inputInfo.inputConfig.get());
     using U = std::underlying_type_t<WindowInfo::Type>;
@@ -185,8 +193,8 @@
     static_assert(std::is_same_v<U, int32_t>);
     proto->set_layout_params_type(static_cast<U>(inputInfo.layoutParamsType));
 
-    LayerProtoHelper::writeToProto({inputInfo.frameLeft, inputInfo.frameTop, inputInfo.frameRight,
-                                    inputInfo.frameBottom},
+    LayerProtoHelper::writeToProto({inputInfo.frame.left, inputInfo.frame.top,
+                                    inputInfo.frame.right, inputInfo.frame.bottom},
                                    [&]() { return proto->mutable_frame(); });
     LayerProtoHelper::writeToProto(inputInfo.touchableRegion,
                                    [&]() { return proto->mutable_touchable_region(); });
@@ -209,7 +217,8 @@
     }
 }
 
-void LayerProtoHelper::writeToProto(const mat4 matrix, ColorTransformProto* colorTransformProto) {
+void LayerProtoHelper::writeToProto(const mat4 matrix,
+                                    perfetto::protos::ColorTransformProto* colorTransformProto) {
     for (int i = 0; i < mat4::ROW_SIZE; i++) {
         for (int j = 0; j < mat4::COL_SIZE; j++) {
             colorTransformProto->add_val(matrix[i][j]);
@@ -217,7 +226,8 @@
     }
 }
 
-void LayerProtoHelper::readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix) {
+void LayerProtoHelper::readFromProto(
+        const perfetto::protos::ColorTransformProto& colorTransformProto, mat4& matrix) {
     for (int i = 0; i < mat4::ROW_SIZE; i++) {
         for (int j = 0; j < mat4::COL_SIZE; j++) {
             matrix[i][j] = colorTransformProto.val(i * mat4::COL_SIZE + j);
@@ -225,7 +235,8 @@
     }
 }
 
-void LayerProtoHelper::writeToProto(const android::BlurRegion region, BlurRegion* proto) {
+void LayerProtoHelper::writeToProto(const android::BlurRegion region,
+                                    perfetto::protos::BlurRegion* proto) {
     proto->set_blur_radius(region.blurRadius);
     proto->set_corner_radius_tl(region.cornerRadiusTL);
     proto->set_corner_radius_tr(region.cornerRadiusTR);
@@ -238,7 +249,8 @@
     proto->set_bottom(region.bottom);
 }
 
-void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion) {
+void LayerProtoHelper::readFromProto(const perfetto::protos::BlurRegion& proto,
+                                     android::BlurRegion& outRegion) {
     outRegion.blurRadius = proto.blur_radius();
     outRegion.cornerRadiusTL = proto.corner_radius_tl();
     outRegion.cornerRadiusTR = proto.corner_radius_tr();
@@ -251,7 +263,8 @@
     outRegion.bottom = proto.bottom();
 }
 
-LayersProto LayerProtoFromSnapshotGenerator::generate(const frontend::LayerHierarchy& root) {
+perfetto::protos::LayersProto LayerProtoFromSnapshotGenerator::generate(
+        const frontend::LayerHierarchy& root) {
     mLayersProto.clear_layers();
     std::unordered_set<uint64_t> stackIdsToSkip;
     if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) {
@@ -310,7 +323,7 @@
 void LayerProtoFromSnapshotGenerator::writeHierarchyToProto(
         const frontend::LayerHierarchy& root, frontend::LayerHierarchy::TraversalPath& path) {
     using Variant = frontend::LayerHierarchy::Variant;
-    LayerProto* layerProto = mLayersProto.add_layers();
+    perfetto::protos::LayerProto* layerProto = mLayersProto.add_layers();
     const frontend::RequestedLayerState& layer = *root.getLayer();
     frontend::LayerSnapshot* snapshot = getSnapshot(path, layer);
     LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags);
@@ -349,7 +362,7 @@
     }
 }
 
-void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo,
+void LayerProtoHelper::writeSnapshotToProto(perfetto::protos::LayerProto* layerInfo,
                                             const frontend::RequestedLayerState& requestedState,
                                             const frontend::LayerSnapshot& snapshot,
                                             uint32_t traceFlags) {
@@ -389,7 +402,7 @@
                                    [&]() { return layerInfo->mutable_screen_bounds(); });
     LayerProtoHelper::writeToProto(snapshot.roundedCorner.cropRect,
                                    [&]() { return layerInfo->mutable_corner_radius_crop(); });
-    layerInfo->set_shadow_radius(snapshot.shadowRadius);
+    layerInfo->set_shadow_radius(snapshot.shadowSettings.length);
 
     layerInfo->set_id(snapshot.uniqueSequence);
     layerInfo->set_original_id(snapshot.sequence);
@@ -446,9 +459,9 @@
                                    [&]() { return layerInfo->mutable_destination_frame(); });
 }
 
-google::protobuf::RepeatedPtrField<DisplayProto> LayerProtoHelper::writeDisplayInfoToProto(
-        const frontend::DisplayInfos& displayInfos) {
-    google::protobuf::RepeatedPtrField<DisplayProto> displays;
+google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
+LayerProtoHelper::writeDisplayInfoToProto(const frontend::DisplayInfos& displayInfos) {
+    google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> displays;
     displays.Reserve(displayInfos.size());
     for (const auto& [layerStack, displayInfo] : displayInfos) {
         auto displayProto = displays.Add();
diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h
index 346685f..20c2260 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -35,39 +35,47 @@
 namespace surfaceflinger {
 class LayerProtoHelper {
 public:
-    static void writePositionToProto(const float x, const float y,
-                                     std::function<PositionProto*()> getPositionProto);
+    static void writePositionToProto(
+            const float x, const float y,
+            std::function<perfetto::protos::PositionProto*()> getPositionProto);
     static void writeSizeToProto(const uint32_t w, const uint32_t h,
-                                 std::function<SizeProto*()> getSizeProto);
-    static void writeToProto(const Rect& rect, std::function<RectProto*()> getRectProto);
-    static void writeToProto(const Rect& rect, RectProto* rectProto);
-    static void readFromProto(const RectProto& proto, Rect& outRect);
+                                 std::function<perfetto::protos::SizeProto*()> getSizeProto);
+    static void writeToProto(const Rect& rect,
+                             std::function<perfetto::protos::RectProto*()> getRectProto);
+    static void writeToProto(const Rect& rect, perfetto::protos::RectProto* rectProto);
+    static void readFromProto(const perfetto::protos::RectProto& proto, Rect& outRect);
     static void writeToProto(const FloatRect& rect,
-                             std::function<FloatRectProto*()> getFloatRectProto);
-    static void writeToProto(const Region& region, std::function<RegionProto*()> getRegionProto);
-    static void writeToProto(const Region& region, RegionProto* regionProto);
-    static void readFromProto(const RegionProto& regionProto, Region& outRegion);
-    static void writeToProto(const half4 color, std::function<ColorProto*()> getColorProto);
+                             std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto);
+    static void writeToProto(const Region& region,
+                             std::function<perfetto::protos::RegionProto*()> getRegionProto);
+    static void writeToProto(const Region& region, perfetto::protos::RegionProto* regionProto);
+    static void readFromProto(const perfetto::protos::RegionProto& regionProto, Region& outRegion);
+    static void writeToProto(const half4 color,
+                             std::function<perfetto::protos::ColorProto*()> getColorProto);
     // This writeToProto for transform is incorrect, but due to backwards compatibility, we can't
     // update Layers to use it. Use writeTransformToProto for any new transform proto data.
     static void writeToProtoDeprecated(const ui::Transform& transform,
-                                       TransformProto* transformProto);
+                                       perfetto::protos::TransformProto* transformProto);
     static void writeTransformToProto(const ui::Transform& transform,
-                                      TransformProto* transformProto);
-    static void writeToProto(const renderengine::ExternalTexture& buffer,
-                             std::function<ActiveBufferProto*()> getActiveBufferProto);
-    static void writeToProto(const gui::WindowInfo& inputInfo,
-                             const wp<Layer>& touchableRegionBounds,
-                             std::function<InputWindowInfoProto*()> getInputWindowInfoProto);
-    static void writeToProto(const mat4 matrix, ColorTransformProto* colorTransformProto);
-    static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix);
-    static void writeToProto(const android::BlurRegion region, BlurRegion*);
-    static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion);
-    static void writeSnapshotToProto(LayerProto* outProto,
+                                      perfetto::protos::TransformProto* transformProto);
+    static void writeToProto(
+            const renderengine::ExternalTexture& buffer,
+            std::function<perfetto::protos::ActiveBufferProto*()> getActiveBufferProto);
+    static void writeToProto(
+            const gui::WindowInfo& inputInfo, const wp<Layer>& touchableRegionBounds,
+            std::function<perfetto::protos::InputWindowInfoProto*()> getInputWindowInfoProto);
+    static void writeToProto(const mat4 matrix,
+                             perfetto::protos::ColorTransformProto* colorTransformProto);
+    static void readFromProto(const perfetto::protos::ColorTransformProto& colorTransformProto,
+                              mat4& matrix);
+    static void writeToProto(const android::BlurRegion region, perfetto::protos::BlurRegion*);
+    static void readFromProto(const perfetto::protos::BlurRegion& proto,
+                              android::BlurRegion& outRegion);
+    static void writeSnapshotToProto(perfetto::protos::LayerProto* outProto,
                                      const frontend::RequestedLayerState& requestedState,
                                      const frontend::LayerSnapshot& snapshot, uint32_t traceFlags);
-    static google::protobuf::RepeatedPtrField<DisplayProto> writeDisplayInfoToProto(
-            const frontend::DisplayInfos&);
+    static google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
+    writeDisplayInfoToProto(const frontend::DisplayInfos&);
 };
 
 class LayerProtoFromSnapshotGenerator {
@@ -80,7 +88,7 @@
             mLegacyLayers(legacyLayers),
             mDisplayInfos(displayInfos),
             mTraceFlags(traceFlags) {}
-    LayersProto generate(const frontend::LayerHierarchy& root);
+    perfetto::protos::LayersProto generate(const frontend::LayerHierarchy& root);
 
 private:
     void writeHierarchyToProto(const frontend::LayerHierarchy& root,
@@ -92,7 +100,7 @@
     const std::unordered_map<uint32_t, sp<Layer>>& mLegacyLayers;
     const frontend::DisplayInfos& mDisplayInfos;
     uint32_t mTraceFlags;
-    LayersProto mLayersProto;
+    perfetto::protos::LayersProto mLayersProto;
     // winscope expects all the layers, so provide a snapshot even if it not currently drawing
     std::unordered_map<frontend::LayerHierarchy::TraversalPath, frontend::LayerSnapshot,
                        frontend::LayerHierarchy::TraversalPathHash>
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index f1fd6db..42676c6 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,104 +16,21 @@
 
 #include <algorithm>
 
-#include "BackgroundExecutor.h"
 #include "Client.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
+#include "Utils/FlagUtils.h"
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <SkCanvas.h>
-#include <SkPaint.h>
-#pragma clang diagnostic pop
-#include <SkBlendMode.h>
-#include <SkRect.h>
 #include <SkSurface.h>
-#include <gui/SurfaceControl.h>
 
 #undef LOG_TAG
 #define LOG_TAG "RefreshRateOverlay"
 
 namespace android {
-namespace {
 
-constexpr int kDigitWidth = 64;
-constexpr int kDigitHeight = 100;
-constexpr int kDigitSpace = 16;
-
-constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
-constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
-constexpr int kBufferHeight = kDigitHeight;
-
-} // namespace
-
-SurfaceControlHolder::~SurfaceControlHolder() {
-    // Hand the sp<SurfaceControl> to the helper thread to release the last
-    // reference. This makes sure that the SurfaceControl is destructed without
-    // SurfaceFlinger::mStateLock held.
-    BackgroundExecutor::getInstance().sendCallbacks(
-            {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
-                                                         SkCanvas& canvas) {
-    const SkRect rect = [&]() {
-        switch (segment) {
-            case Segment::Upper:
-                return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
-            case Segment::UpperLeft:
-                return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2);
-            case Segment::UpperRight:
-                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
-                                        kDigitHeight / 2);
-            case Segment::Middle:
-                return SkRect::MakeLTRB(left, kDigitHeight / 2 - kDigitSpace / 2,
-                                        left + kDigitWidth, kDigitHeight / 2 + kDigitSpace / 2);
-            case Segment::LowerLeft:
-                return SkRect::MakeLTRB(left, kDigitHeight / 2, left + kDigitSpace, kDigitHeight);
-            case Segment::LowerRight:
-                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2,
-                                        left + kDigitWidth, kDigitHeight);
-            case Segment::Bottom:
-                return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
-                                        kDigitHeight);
-        }
-    }();
-
-    SkPaint paint;
-    paint.setColor(color);
-    paint.setBlendMode(SkBlendMode::kSrc);
-    canvas.drawRect(rect, paint);
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor color,
-                                                       SkCanvas& canvas) {
-    if (digit < 0 || digit > 9) return;
-
-    if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
-        digit == 8 || digit == 9)
-        drawSegment(Segment::Upper, left, color, canvas);
-    if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
-        drawSegment(Segment::UpperLeft, left, color, canvas);
-    if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
-        digit == 8 || digit == 9)
-        drawSegment(Segment::UpperRight, left, color, canvas);
-    if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
-        digit == 9)
-        drawSegment(Segment::Middle, left, color, canvas);
-    if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
-        drawSegment(Segment::LowerLeft, left, color, canvas);
-    if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
-        digit == 7 || digit == 8 || digit == 9)
-        drawSegment(Segment::LowerRight, left, color, canvas);
-    if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
-        digit == 9)
-        drawSegment(Segment::Bottom, left, color, canvas);
-}
-
-auto RefreshRateOverlay::SevenSegmentDrawer::draw(int displayFps, int renderFps, SkColor color,
-                                                  ui::Transform::RotationFlags rotation,
-                                                  ftl::Flags<Features> features) -> Buffers {
+auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color,
+                              ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
+        -> Buffers {
     const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
 
     Buffers buffers;
@@ -148,32 +65,38 @@
         LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "RefreshRateOverlay: Buffer failed to allocate: %d",
                             bufferStatus);
 
-        sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(bufferWidth, bufferHeight);
+        sk_sp<SkSurface> surface = SkSurfaces::Raster(
+                SkImageInfo::MakeN32Premul(bufferWidth, bufferHeight));
         SkCanvas* canvas = surface->getCanvas();
         canvas->setMatrix(canvasTransform);
 
         int left = 0;
-        drawNumber(displayFps, left, color, *canvas);
+        drawNumber(vsyncRate, left, color, *canvas);
         left += 3 * (kDigitWidth + kDigitSpace);
         if (features.test(Features::Spinner)) {
             switch (i) {
                 case 0:
-                    drawSegment(Segment::Upper, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::Upper, left, color, *canvas);
                     break;
                 case 1:
-                    drawSegment(Segment::UpperRight, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperRight, left, color,
+                                               *canvas);
                     break;
                 case 2:
-                    drawSegment(Segment::LowerRight, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerRight, left, color,
+                                               *canvas);
                     break;
                 case 3:
-                    drawSegment(Segment::Bottom, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::Bottom, left, color,
+                                               *canvas);
                     break;
                 case 4:
-                    drawSegment(Segment::LowerLeft, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerLeft, left, color,
+                                               *canvas);
                     break;
                 case 5:
-                    drawSegment(Segment::UpperLeft, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperLeft, left, color,
+                                               *canvas);
                     break;
             }
         }
@@ -199,34 +122,40 @@
     return buffers;
 }
 
-void RefreshRateOverlay::SevenSegmentDrawer::drawNumber(int number, int left, SkColor color,
-                                                        SkCanvas& canvas) {
+void RefreshRateOverlay::drawNumber(int number, int left, SkColor color, SkCanvas& canvas) {
     if (number < 0 || number >= 1000) return;
 
     if (number >= 100) {
-        drawDigit(number / 100, left, color, canvas);
+        SegmentDrawer::drawDigit(number / 100, left, color, canvas);
     }
     left += kDigitWidth + kDigitSpace;
 
     if (number >= 10) {
-        drawDigit((number / 10) % 10, left, color, canvas);
+        SegmentDrawer::drawDigit((number / 10) % 10, left, color, canvas);
     }
     left += kDigitWidth + kDigitSpace;
 
-    drawDigit(number % 10, left, color, canvas);
+    SegmentDrawer::drawDigit(number % 10, left, color, canvas);
 }
 
-std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder() {
-    sp<SurfaceControl> surfaceControl =
-            SurfaceComposerClient::getDefault()
-                    ->createSurface(String8("RefreshRateOverlay"), kBufferWidth, kBufferHeight,
-                                    PIXEL_FORMAT_RGBA_8888,
-                                    ISurfaceComposerClient::eFXSurfaceBufferState);
-    return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+std::unique_ptr<RefreshRateOverlay> RefreshRateOverlay::create(FpsRange range,
+                                                               ftl::Flags<Features> features) {
+    std::unique_ptr<RefreshRateOverlay> overlay =
+            std::make_unique<RefreshRateOverlay>(ConstructorTag{}, range, features);
+    if (overlay->initCheck()) {
+        return overlay;
+    }
+
+    ALOGE("%s: Failed to create RefreshRateOverlay", __func__);
+    return {};
 }
 
-RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags<Features> features)
-      : mFpsRange(fpsRange), mFeatures(features), mSurfaceControl(createSurfaceControlHolder()) {
+RefreshRateOverlay::RefreshRateOverlay(ConstructorTag, FpsRange fpsRange,
+                                       ftl::Flags<Features> features)
+      : mFpsRange(fpsRange),
+        mFeatures(features),
+        mSurfaceControl(
+                SurfaceControlHolder::createSurfaceControlHolder(String8("RefreshRateOverlay"))) {
     if (!mSurfaceControl) {
         ALOGE("%s: Failed to create buffer state layer", __func__);
         return;
@@ -238,7 +167,11 @@
             .apply();
 }
 
-auto RefreshRateOverlay::getOrCreateBuffers(Fps displayFps, Fps renderFps) -> const Buffers& {
+bool RefreshRateOverlay::initCheck() const {
+    return mSurfaceControl != nullptr;
+}
+
+auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, Fps renderFps) -> const Buffers& {
     static const Buffers kNoBuffers;
     if (!mSurfaceControl) return kNoBuffers;
 
@@ -265,16 +198,16 @@
     createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
     BufferCache::const_iterator it =
-            mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint});
+            mBufferCache.find({vsyncRate.getIntValue(), renderFps.getIntValue(), transformHint});
     if (it == mBufferCache.end()) {
         // HWC minFps is not known by the framework in order
         // to consider lower rates we set minFps to 0.
         const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue();
         const int maxFps = mFpsRange.max.getIntValue();
 
-        // Clamp to the range. The current displayFps may be outside of this range if the display
+        // Clamp to the range. The current vsyncRate may be outside of this range if the display
         // has changed its set of supported refresh rates.
-        const int displayIntFps = std::clamp(displayFps.getIntValue(), minFps, maxFps);
+        const int displayIntFps = std::clamp(vsyncRate.getIntValue(), minFps, maxFps);
         const int renderIntFps = renderFps.getIntValue();
 
         // Ensure non-zero range to avoid division by zero.
@@ -295,8 +228,7 @@
 
         const SkColor color = colorBase.toSkColor();
 
-        auto buffers = SevenSegmentDrawer::draw(displayIntFps, renderIntFps, color, transformHint,
-                                                mFeatures);
+        auto buffers = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures);
         it = mBufferCache
                      .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
                      .first;
@@ -328,17 +260,25 @@
     createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
-void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) {
-    mDisplayFps = displayFps;
+void RefreshRateOverlay::changeRefreshRate(Fps vsyncRate, Fps renderFps) {
+    mVsyncRate = vsyncRate;
     mRenderFps = renderFps;
-    const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame];
+    const auto buffer = getOrCreateBuffers(vsyncRate, renderFps)[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
 }
 
-void RefreshRateOverlay::animate() {
-    if (!mFeatures.test(Features::Spinner) || !mDisplayFps) return;
+void RefreshRateOverlay::changeRenderRate(Fps renderFps) {
+    if (mFeatures.test(Features::RenderRate) && mVsyncRate && flagutils::vrrConfigEnabled()) {
+        mRenderFps = renderFps;
+        const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame];
+        createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
+    }
+}
 
-    const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps);
+void RefreshRateOverlay::animate() {
+    if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return;
+
+    const auto& buffers = getOrCreateBuffers(*mVsyncRate, *mRenderFps);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 0b89b8e..0fec470 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -16,12 +16,12 @@
 
 #pragma once
 
-#include <SkColor.h>
+#include "Utils/OverlayUtils.h"
+
 #include <vector>
 
 #include <ftl/flags.h>
 #include <ftl/small_map.h>
-#include <gui/SurfaceComposerClient.h>
 #include <ui/LayerStack.h>
 #include <ui/Size.h>
 #include <ui/Transform.h>
@@ -34,23 +34,13 @@
 namespace android {
 
 class GraphicBuffer;
-class SurfaceControl;
 class SurfaceFlinger;
 
-// Helper class to delete the SurfaceControl on a helper thread as
-// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
-class SurfaceControlHolder {
-public:
-    explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
-    ~SurfaceControlHolder();
-
-    const sp<SurfaceControl>& get() const { return mSurfaceControl; }
-
-private:
-    sp<SurfaceControl> mSurfaceControl;
-};
-
 class RefreshRateOverlay {
+private:
+    // Effectively making the constructor private, while keeping std::make_unique work
+    struct ConstructorTag {};
+
 public:
     enum class Features {
         Spinner = 1 << 0,
@@ -59,41 +49,37 @@
         SetByHwc = 1 << 3,
     };
 
-    RefreshRateOverlay(FpsRange, ftl::Flags<Features>);
+    static std::unique_ptr<RefreshRateOverlay> create(FpsRange, ftl::Flags<Features>);
 
     void setLayerStack(ui::LayerStack);
     void setViewport(ui::Size);
     void changeRefreshRate(Fps, Fps);
+    void changeRenderRate(Fps);
     void animate();
     bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); }
 
+    RefreshRateOverlay(ConstructorTag, FpsRange, ftl::Flags<Features>);
+
 private:
+    bool initCheck() const;
+
     using Buffers = std::vector<sp<GraphicBuffer>>;
 
-    class SevenSegmentDrawer {
-    public:
-        static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
-                            ftl::Flags<Features>);
-
-    private:
-        enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
-
-        static void drawSegment(Segment, int left, SkColor, SkCanvas&);
-        static void drawDigit(int digit, int left, SkColor, SkCanvas&);
-        static void drawNumber(int number, int left, SkColor, SkCanvas&);
-    };
+    static Buffers draw(int vsyncRate, int renderFps, SkColor, ui::Transform::RotationFlags,
+                        ftl::Flags<Features>);
+    static void drawNumber(int number, int left, SkColor, SkCanvas&);
 
     const Buffers& getOrCreateBuffers(Fps, Fps);
 
     SurfaceComposerClient::Transaction createTransaction() const;
 
     struct Key {
-        int displayFps;
+        int vsyncRate;
         int renderFps;
         ui::Transform::RotationFlags flags;
 
         bool operator==(Key other) const {
-            return displayFps == other.displayFps && renderFps == other.renderFps &&
+            return vsyncRate == other.vsyncRate && renderFps == other.renderFps &&
                     flags == other.flags;
         }
     };
@@ -101,7 +87,7 @@
     using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
     BufferCache mBufferCache;
 
-    std::optional<Fps> mDisplayFps;
+    std::optional<Fps> mVsyncRate;
     std::optional<Fps> mRenderFps;
     size_t mFrame = 0;
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 281b0ae..9a55c94 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -38,6 +38,7 @@
 #include <cutils/sched_policy.h>
 
 #include <gui/DisplayEventReceiver.h>
+#include <gui/SchedulingPolicy.h>
 
 #include <utils/Errors.h>
 #include <utils/Trace.h>
@@ -111,6 +112,15 @@
     return event;
 }
 
+DisplayEventReceiver::Event makeHotplugError(nsecs_t timestamp, int32_t connectionError) {
+    DisplayEventReceiver::Event event;
+    PhysicalDisplayId unusedDisplayId;
+    event.header = {DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, unusedDisplayId, timestamp};
+    event.hotplug.connected = false;
+    event.hotplug.connectionError = connectionError;
+    return event;
+}
+
 DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t timestamp,
                                       uint32_t count, nsecs_t expectedPresentationTime,
                                       nsecs_t deadlineTimestamp) {
@@ -162,10 +172,8 @@
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
-                                             ResyncCallback resyncCallback,
                                              EventRegistrationFlags eventRegistration)
-      : resyncCallback(std::move(resyncCallback)),
-        mOwnerUid(callingUid),
+      : mOwnerUid(callingUid),
         mEventRegistration(eventRegistration),
         mEventThread(eventThread),
         mChannel(gui::BitTube::DefaultSize) {}
@@ -211,6 +219,10 @@
     return binder::Status::ok();
 }
 
+binder::Status EventThreadConnection::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    return gui::getSchedulingPolicy(outPolicy);
+}
+
 status_t EventThreadConnection::postEvent(const DisplayEventReceiver::Event& event) {
     constexpr auto toStatus = [](ssize_t size) {
         return size < 0 ? status_t(size) : status_t(NO_ERROR);
@@ -241,9 +253,7 @@
 
 EventThread::EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule> vsyncSchedule,
                          android::frametimeline::TokenManager* tokenManager,
-                         ThrottleVsyncCallback throttleVsyncCallback,
-                         GetVsyncPeriodFunction getVsyncPeriodFunction,
-                         std::chrono::nanoseconds workDuration,
+                         IEventThreadCallback& callback, std::chrono::nanoseconds workDuration,
                          std::chrono::nanoseconds readyDuration)
       : mThreadName(name),
         mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0),
@@ -252,11 +262,7 @@
         mVsyncSchedule(std::move(vsyncSchedule)),
         mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name),
         mTokenManager(tokenManager),
-        mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
-        mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) {
-    LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr,
-            "getVsyncPeriodFunction must not be null");
-
+        mCallback(callback) {
     mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
         std::unique_lock<std::mutex> lock(mMutex);
         threadMain(lock);
@@ -298,10 +304,15 @@
 }
 
 sp<EventThreadConnection> EventThread::createEventConnection(
-        ResyncCallback resyncCallback, EventRegistrationFlags eventRegistration) const {
-    return sp<EventThreadConnection>::make(const_cast<EventThread*>(this),
-                                           IPCThreadState::self()->getCallingUid(),
-                                           std::move(resyncCallback), eventRegistration);
+        EventRegistrationFlags eventRegistration) const {
+    auto connection = sp<EventThreadConnection>::make(const_cast<EventThread*>(this),
+                                                      IPCThreadState::self()->getCallingUid(),
+                                                      eventRegistration);
+    if (flags::misc1()) {
+        const int policy = SCHED_FIFO;
+        connection->setMinSchedulerPolicy(policy, sched_get_priority_min(policy));
+    }
+    return connection;
 }
 
 status_t EventThread::registerDisplayEventConnection(const sp<EventThreadConnection>& connection) {
@@ -344,9 +355,7 @@
 }
 
 void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
-    if (connection->resyncCallback) {
-        connection->resyncCallback();
-    }
+    mCallback.resync();
 
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -362,20 +371,18 @@
         const sp<EventThreadConnection>& connection) const {
     // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
     // way to get vsync data (instead of posting callbacks to Choreographer).
-    if (connection->resyncCallback) {
-        connection->resyncCallback();
-    }
+    mCallback.resync();
 
     VsyncEventData vsyncEventData;
-    nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
-    vsyncEventData.frameInterval = frameInterval;
+    const Period frameInterval = mCallback.getVsyncPeriod(connection->mOwnerUid);
+    vsyncEventData.frameInterval = frameInterval.ns();
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
         const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
                 systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
-    generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC),
+    generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
                           presentTime, deadline);
     return vsyncEventData;
 }
@@ -408,6 +415,13 @@
     mCondition.notify_all();
 }
 
+void EventThread::onHotplugConnectionError(int32_t errorCode) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeHotplugError(systemTime(), errorCode));
+    mCondition.notify_all();
+}
+
 void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) {
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -427,11 +441,6 @@
     mCondition.notify_all();
 }
 
-size_t EventThread::getEventThreadConnectionCount() {
-    std::lock_guard<std::mutex> lock(mMutex);
-    return mDisplayEventConnections.size();
-}
-
 void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
     DisplayEventConsumers consumers;
 
@@ -444,11 +453,15 @@
             mPendingEvents.pop_front();
 
             if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
-                if (event->hotplug.connected && !mVSyncState) {
-                    mVSyncState.emplace(event->header.displayId);
-                } else if (!event->hotplug.connected && mVSyncState &&
-                           mVSyncState->displayId == event->header.displayId) {
-                    mVSyncState.reset();
+                if (event->hotplug.connectionError == 0) {
+                    if (event->hotplug.connected && !mVSyncState) {
+                        mVSyncState.emplace(event->header.displayId);
+                    } else if (!event->hotplug.connected && mVSyncState &&
+                               mVSyncState->displayId == event->header.displayId) {
+                        mVSyncState.reset();
+                    }
+                } else {
+                    // Ignore vsync stuff on an error.
                 }
             }
         }
@@ -534,9 +547,9 @@
                                             connection->frameRate);
         }
 
-        return mThrottleVsyncCallback &&
-                mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
-                                       connection->mOwnerUid);
+        const auto expectedPresentTime =
+                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime());
+        return mCallback.throttleVsync(expectedPresentTime, connection->mOwnerUid);
     };
 
     switch (event.header.type) {
@@ -656,9 +669,9 @@
     for (const auto& consumer : consumers) {
         DisplayEventReceiver::Event copy = event;
         if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
-            const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid);
-            copy.vsync.vsyncData.frameInterval = frameInterval;
-            generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp,
+            const Period frameInterval = mCallback.getVsyncPeriod(consumer->mOwnerUid);
+            copy.vsync.vsyncData.frameInterval = frameInterval.ns();
+            generateFrameTimeline(copy.vsync.vsyncData, frameInterval.ns(), copy.header.timestamp,
                                   event.vsync.vsyncData.preferredExpectedPresentationTime(),
                                   event.vsync.vsyncData.preferredDeadlineTimestamp());
         }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 684745b..7842318 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -54,7 +54,6 @@
 
 // ---------------------------------------------------------------------------
 
-using ResyncCallback = std::function<void()>;
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
 enum class VSyncRequest {
@@ -69,7 +68,7 @@
 
 class EventThreadConnection : public gui::BnDisplayEventConnection {
 public:
-    EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
+    EventThreadConnection(EventThread*, uid_t callingUid,
                           EventRegistrationFlags eventRegistration = {});
     virtual ~EventThreadConnection();
 
@@ -79,9 +78,7 @@
     binder::Status setVsyncRate(int rate) override;
     binder::Status requestNextVsync() override; // asynchronous
     binder::Status getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) override;
-
-    // Called in response to requestNextVsync.
-    const ResyncCallback resyncCallback;
+    binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
 
     VSyncRequest vsyncRequest = VSyncRequest::None;
     const uid_t mOwnerUid;
@@ -104,13 +101,15 @@
     virtual ~EventThread();
 
     virtual sp<EventThreadConnection> createEventConnection(
-            ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0;
+            EventRegistrationFlags eventRegistration = {}) const = 0;
 
     // Feed clients with fake VSYNC, e.g. while the display is off.
     virtual void enableSyntheticVsync(bool) = 0;
 
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
+    virtual void onHotplugConnectionError(int32_t connectionError) = 0;
+
     // called when SF changes the active mode and apps needs to be notified about the change
     virtual void onModeChanged(const scheduler::FrameRateMode&) = 0;
 
@@ -131,26 +130,28 @@
     virtual VsyncEventData getLatestVsyncEventData(
             const sp<EventThreadConnection>& connection) const = 0;
 
-    // Retrieves the number of event connections tracked by this EventThread.
-    virtual size_t getEventThreadConnectionCount() = 0;
-
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
 };
 
+struct IEventThreadCallback {
+    virtual ~IEventThreadCallback() = default;
+
+    virtual bool throttleVsync(TimePoint, uid_t) = 0;
+    virtual Period getVsyncPeriod(uid_t) = 0;
+    virtual void resync() = 0;
+};
+
 namespace impl {
 
 class EventThread : public android::EventThread {
 public:
-    using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
-    using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>;
-
     EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule>,
-                frametimeline::TokenManager*, ThrottleVsyncCallback, GetVsyncPeriodFunction,
+                frametimeline::TokenManager*, IEventThreadCallback& callback,
                 std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration);
     ~EventThread();
 
     sp<EventThreadConnection> createEventConnection(
-            ResyncCallback, EventRegistrationFlags eventRegistration = {}) const override;
+            EventRegistrationFlags eventRegistration = {}) const override;
 
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
@@ -162,6 +163,8 @@
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
+    void onHotplugConnectionError(int32_t connectionError) override;
+
     void onModeChanged(const scheduler::FrameRateMode&) override;
 
     void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
@@ -172,8 +175,6 @@
     void setDuration(std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration) override;
 
-    size_t getEventThreadConnectionCount() override;
-
     void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex);
 
 private:
@@ -215,8 +216,7 @@
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
 
-    const ThrottleVsyncCallback mThrottleVsyncCallback;
-    const GetVsyncPeriodFunction mGetVsyncPeriodFunction;
+    IEventThreadCallback& mCallback;
 
     std::thread mThread;
     mutable std::mutex mMutex;
diff --git a/services/surfaceflinger/Scheduler/FrameRateCompatibility.h b/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
new file mode 100644
index 0000000..405c982
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android::scheduler {
+// FrameRateCompatibility specifies how we should interpret the frame rate associated with
+// the layer.
+enum class FrameRateCompatibility {
+    Default, // Layer didn't specify any specific handling strategy
+
+    Min, // Layer needs the minimum frame rate.
+
+    Exact, // Layer needs the exact frame rate.
+
+    ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the
+                     // content properly. Any other value will result in a pull down.
+
+    NoVote, // Layer doesn't have any requirements for the refresh rate and
+            // should not be considered when the display refresh rate is determined.
+
+    ftl_last = NoVote
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index badbf53..3b61de7 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -29,6 +29,7 @@
     virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
+    virtual void onChoreographerAttached() = 0;
 
 protected:
     ~ISchedulerCallback() = default;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 6adffc9..069d89b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -40,8 +40,9 @@
 namespace {
 
 bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
-    // Layers with an explicit vote are always kept active
-    if (info.getSetFrameRateVote().rate.isValid()) {
+    // Layers with an explicit frame rate or frame rate category are always kept active,
+    // but ignore NoVote.
+    if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
         return true;
     }
 
@@ -70,16 +71,17 @@
     traceType(LayerHistory::LayerVoteType::ExplicitExact, fps);
     traceType(LayerHistory::LayerVoteType::Min, 1);
     traceType(LayerHistory::LayerVoteType::Max, 1);
+    traceType(LayerHistory::LayerVoteType::ExplicitCategory, 1);
 
     ALOGD("%s: %s @ %d Hz", __FUNCTION__, info.getName().c_str(), fps);
 }
 
-LayerHistory::LayerVoteType getVoteType(LayerInfo::FrameRateCompatibility compatibility,
+LayerHistory::LayerVoteType getVoteType(FrameRateCompatibility compatibility,
                                         bool contentDetectionEnabled) {
     LayerHistory::LayerVoteType voteType;
-    if (!contentDetectionEnabled || compatibility == LayerInfo::FrameRateCompatibility::NoVote) {
+    if (!contentDetectionEnabled || compatibility == FrameRateCompatibility::NoVote) {
         voteType = LayerHistory::LayerVoteType::NoVote;
-    } else if (compatibility == LayerInfo::FrameRateCompatibility::Min) {
+    } else if (compatibility == FrameRateCompatibility::Min) {
         voteType = LayerHistory::LayerVoteType::Min;
     } else {
         voteType = LayerHistory::LayerVoteType::Heuristic;
@@ -131,22 +133,6 @@
     const auto& info = layerPair->second;
     info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps);
 
-    // Set frame rate to attached choreographer.
-    // TODO(b/260898223): Change to use layer hierarchy and handle frame rate vote.
-    if (updateType == LayerUpdateType::SetFrameRate) {
-        auto range = mAttachedChoreographers.equal_range(id);
-        auto it = range.first;
-        while (it != range.second) {
-            sp<EventThreadConnection> choreographerConnection = it->second.promote();
-            if (choreographerConnection) {
-                choreographerConnection->frameRate = layerProps.setFrameRateVote.rate;
-                it++;
-            } else {
-                it = mAttachedChoreographers.erase(it);
-            }
-        }
-    }
-
     // Activate layer if inactive.
     if (found == LayerStatus::LayerInInactiveMap) {
         mActiveLayerInfos.insert(
@@ -155,20 +141,20 @@
     }
 }
 
-void LayerHistory::setDefaultFrameRateCompatibility(Layer* layer, bool contentDetectionEnabled) {
+void LayerHistory::setDefaultFrameRateCompatibility(int32_t id,
+                                                    FrameRateCompatibility frameRateCompatibility,
+                                                    bool contentDetectionEnabled) {
     std::lock_guard lock(mLock);
-    auto id = layer->getSequence();
 
     auto [found, layerPair] = findLayer(id);
     if (found == LayerStatus::NotFound) {
         // Offscreen layer
-        ALOGV("%s: %s not registered", __func__, layer->getName().c_str());
+        ALOGV("%s: %d not registered", __func__, id);
         return;
     }
 
     const auto& info = layerPair->second;
-    info->setDefaultLayerVote(
-            getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled));
+    info->setDefaultLayerVote(getVoteType(frameRateCompatibility, contentDetectionEnabled));
 }
 
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
@@ -187,27 +173,33 @@
               layerFocused ? "" : "not");
 
         ATRACE_FORMAT("%s", info->getName().c_str());
-        const auto vote = info->getRefreshRateVote(selector, now);
-        // Skip NoVote layer as those don't have any requirements
-        if (vote.type == LayerVoteType::NoVote) {
-            continue;
-        }
+        const auto votes = info->getRefreshRateVote(selector, now);
+        for (LayerInfo::LayerVote vote : votes) {
+            if (vote.isNoVote()) {
+                continue;
+            }
 
-        // Compute the layer's position on the screen
-        const Rect bounds = Rect(info->getBounds());
-        const ui::Transform transform = info->getTransform();
-        constexpr bool roundOutwards = true;
-        Rect transformed = transform.transform(bounds, roundOutwards);
+            // Compute the layer's position on the screen
+            const Rect bounds = Rect(info->getBounds());
+            const ui::Transform transform = info->getTransform();
+            constexpr bool roundOutwards = true;
+            Rect transformed = transform.transform(bounds, roundOutwards);
 
-        const float layerArea = transformed.getWidth() * transformed.getHeight();
-        float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
-        ATRACE_FORMAT_INSTANT("%s %s (%d%)", ftl::enum_string(vote.type).c_str(),
-                              to_string(vote.fps).c_str(), weight * 100);
-        summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
-                           vote.seamlessness, weight, layerFocused});
+            const float layerArea = transformed.getWidth() * transformed.getHeight();
+            float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+            const std::string categoryString = vote.category == FrameRateCategory::Default
+                    ? ""
+                    : base::StringPrintf("category=%s", ftl::enum_string(vote.category).c_str());
+            ATRACE_FORMAT_INSTANT("%s %s %s (%d%)", ftl::enum_string(vote.type).c_str(),
+                                  to_string(vote.fps).c_str(), categoryString.c_str(),
+                                  weight * 100);
+            summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
+                               vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly,
+                               weight, layerFocused});
 
-        if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(*info, vote.type, vote.fps.getIntValue());
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(*info, vote.type, vote.fps.getIntValue());
+            }
         }
     }
 
@@ -244,7 +236,7 @@
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
             const auto voteType = [&]() {
-                switch (frameRate.type) {
+                switch (frameRate.vote.type) {
                     case Layer::FrameRateCompatibility::Default:
                         return LayerVoteType::ExplicitDefault;
                     case Layer::FrameRateCompatibility::Min:
@@ -258,9 +250,10 @@
                 }
             }();
 
-            if (frameRate.rate.isValid() || voteType == LayerVoteType::NoVote) {
+            if (frameRate.isValid()) {
                 const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
-                info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
+                info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
+                                    frameRate.category});
             } else {
                 info->resetLayerVote();
             }
@@ -301,12 +294,6 @@
     return 0.f;
 }
 
-void LayerHistory::attachChoreographer(int32_t layerId,
-                                       const sp<EventThreadConnection>& choreographerConnection) {
-    std::lock_guard lock(mLock);
-    mAttachedChoreographers.insert({layerId, wp<EventThreadConnection>(choreographerConnection)});
-}
-
 auto LayerHistory::findLayer(int32_t id) -> std::pair<LayerStatus, LayerPair*> {
     // the layer could be in either the active or inactive map, try both
     auto it = mActiveLayerInfos.find(id);
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 6f07e3b..bac1ec6 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -29,6 +29,7 @@
 
 #include "EventThread.h"
 
+#include "FrameRateCompatibility.h"
 #include "RefreshRateSelector.h"
 
 namespace android {
@@ -70,7 +71,8 @@
 
     // Updates the default frame rate compatibility which takes effect when the app
     // does not set a preference for refresh rate.
-    void setDefaultFrameRateCompatibility(Layer*, bool contentDetectionEnabled);
+    void setDefaultFrameRateCompatibility(int32_t id, FrameRateCompatibility frameRateCompatibility,
+                                          bool contentDetectionEnabled);
 
     using Summary = std::vector<RefreshRateSelector::LayerRequirement>;
 
@@ -85,13 +87,11 @@
     // return the frames per second of the layer with the given sequence id.
     float getLayerFramerate(nsecs_t now, int32_t id) const;
 
-    void attachChoreographer(int32_t layerId,
-                             const sp<EventThreadConnection>& choreographerConnection);
-
     bool isSmallDirtyArea(uint32_t dirtyArea, float threshold) const;
 
 private:
     friend class LayerHistoryTest;
+    friend class LayerHistoryIntegrationTest;
     friend class TestableScheduler;
 
     using LayerPair = std::pair<Layer*, std::unique_ptr<LayerInfo>>;
@@ -127,10 +127,6 @@
     LayerInfos mActiveLayerInfos GUARDED_BY(mLock);
     LayerInfos mInactiveLayerInfos GUARDED_BY(mLock);
 
-    // Map keyed by layer ID (sequence) to choreographer connections.
-    std::unordered_multimap<int32_t, wp<EventThreadConnection>> mAttachedChoreographers
-            GUARDED_BY(mLock);
-
     uint32_t mDisplayArea = 0;
 
     // Whether to emit systrace output and debug logs.
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 875e870..36f2475 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -26,10 +26,12 @@
 #include <algorithm>
 #include <utility>
 
+#include <android/native_window.h>
 #include <cutils/compiler.h>
 #include <cutils/trace.h>
 #include <ftl/enum.h>
 #include <gui/TraceUtils.h>
+#include <system/window.h>
 
 #undef LOG_TAG
 #define LOG_TAG "LayerInfo"
@@ -295,19 +297,38 @@
                                                : std::nullopt;
 }
 
-LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
-                                                   nsecs_t now) {
+LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
+                                                          nsecs_t now) {
     ATRACE_CALL();
+    LayerInfo::RefreshRateVotes votes;
+
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
-        ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
-        return mLayerVote;
+        if (mLayerVote.category != FrameRateCategory::Default) {
+            ATRACE_FORMAT_INSTANT("ExplicitCategory (%s)",
+                                  ftl::enum_string(mLayerVote.category).c_str());
+            ALOGV("%s uses frame rate category: %d", mName.c_str(),
+                  static_cast<int>(mLayerVote.category));
+            votes.push_back({LayerHistory::LayerVoteType::ExplicitCategory, Fps(),
+                             Seamlessness::Default, mLayerVote.category,
+                             mLayerVote.categorySmoothSwitchOnly});
+        }
+
+        if (mLayerVote.fps.isValid() ||
+            mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
+            ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
+            ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
+            votes.push_back(mLayerVote);
+        }
+
+        return votes;
     }
 
     if (isAnimating(now)) {
         ATRACE_FORMAT_INSTANT("animating");
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animating = true;
-        return {LayerHistory::LayerVoteType::Max, Fps()};
+        votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
+        return votes;
     }
 
     const LayerInfo::Frequent frequent = isFrequent(now);
@@ -319,7 +340,8 @@
         mLastSmallDirtyCount = 0;
         // Infrequent layers vote for minimal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
-        return {LayerHistory::LayerVoteType::Min, Fps()};
+        votes.push_back({LayerHistory::LayerVoteType::Min, Fps()});
+        return votes;
     }
 
     if (frequent.clearHistory) {
@@ -330,17 +352,22 @@
     if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
         ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
         ALOGV("%s is small dirty", mName.c_str());
-        return {LayerHistory::LayerVoteType::NoVote, Fps()};
+        votes.push_back({LayerHistory::LayerVoteType::NoVote, Fps()});
+        return votes;
     }
 
     auto refreshRate = calculateRefreshRateIfPossible(selector, now);
     if (refreshRate.has_value()) {
+        ATRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
-        return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+        votes.push_back({LayerHistory::LayerVoteType::Heuristic, refreshRate.value()});
+        return votes;
     }
 
+    ATRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
-    return {LayerHistory::LayerVoteType::Max, Fps()};
+    votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
+    return votes;
 }
 
 const char* LayerInfo::getTraceTag(LayerHistory::LayerVoteType type) const {
@@ -429,6 +456,80 @@
     return consistent;
 }
 
+FrameRateCompatibility LayerInfo::FrameRate::convertCompatibility(int8_t compatibility) {
+    switch (compatibility) {
+        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT:
+            return FrameRateCompatibility::Default;
+        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
+            return FrameRateCompatibility::ExactOrMultiple;
+        case ANATIVEWINDOW_FRAME_RATE_EXACT:
+            return FrameRateCompatibility::Exact;
+        case ANATIVEWINDOW_FRAME_RATE_MIN:
+            return FrameRateCompatibility::Min;
+        case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
+            return FrameRateCompatibility::NoVote;
+        default:
+            LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
+            return FrameRateCompatibility::Default;
+    }
+}
+
+Seamlessness LayerInfo::FrameRate::convertChangeFrameRateStrategy(int8_t strategy) {
+    switch (strategy) {
+        case ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
+            return Seamlessness::OnlySeamless;
+        case ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS:
+            return Seamlessness::SeamedAndSeamless;
+        default:
+            LOG_ALWAYS_FATAL("Invalid change frame sate strategy value %d", strategy);
+            return Seamlessness::Default;
+    }
+}
+
+FrameRateCategory LayerInfo::FrameRate::convertCategory(int8_t category) {
+    switch (category) {
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT:
+            return FrameRateCategory::Default;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE:
+            return FrameRateCategory::NoPreference;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_LOW:
+            return FrameRateCategory::Low;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL:
+            return FrameRateCategory::Normal;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH:
+            return FrameRateCategory::High;
+        default:
+            LOG_ALWAYS_FATAL("Invalid frame rate category value %d", category);
+            return FrameRateCategory::Default;
+    }
+}
+
+LayerInfo::FrameRateSelectionStrategy LayerInfo::convertFrameRateSelectionStrategy(
+        int8_t strategy) {
+    switch (strategy) {
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
+            return FrameRateSelectionStrategy::Self;
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN:
+            return FrameRateSelectionStrategy::OverrideChildren;
+        default:
+            LOG_ALWAYS_FATAL("Invalid frame rate selection strategy value %d", strategy);
+            return FrameRateSelectionStrategy::Self;
+    }
+}
+
+bool LayerInfo::FrameRate::isNoVote() const {
+    return vote.type == FrameRateCompatibility::NoVote;
+}
+
+bool LayerInfo::FrameRate::isValid() const {
+    return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
+}
+
+std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) {
+    return stream << "{rate=" << rate.vote.rate << " type=" << ftl::enum_string(rate.vote.type)
+                  << " seamlessness=" << ftl::enum_string(rate.vote.seamlessness) << '}';
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 122796b..7d3cffa 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -25,8 +25,10 @@
 #include <ui/Transform.h>
 #include <utils/Timers.h>
 
+#include <scheduler/Fps.h>
 #include <scheduler/Seamlessness.h>
 
+#include "FrameRateCompatibility.h"
 #include "LayerHistory.h"
 #include "RefreshRateSelector.h"
 
@@ -60,6 +62,7 @@
     static constexpr size_t kNumSmallDirtyThreshold = 2;
 
     friend class LayerHistoryTest;
+    friend class LayerHistoryIntegrationTest;
     friend class LayerInfoTest;
 
 public:
@@ -68,44 +71,57 @@
         LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
         Fps fps;
         Seamlessness seamlessness = Seamlessness::Default;
+        FrameRateCategory category = FrameRateCategory::Default;
+        bool categorySmoothSwitchOnly = false;
+
+        // Returns true if the layer explicitly should contribute to frame rate scoring.
+        bool isNoVote() const { return RefreshRateSelector::isNoVote(type); }
     };
 
-    // FrameRateCompatibility specifies how we should interpret the frame rate associated with
-    // the layer.
-    enum class FrameRateCompatibility {
-        Default, // Layer didn't specify any specific handling strategy
+    using RefreshRateVotes = ftl::SmallVector<LayerInfo::LayerVote, 2>;
 
-        Min, // Layer needs the minimum frame rate.
+    enum class FrameRateSelectionStrategy {
+        Self,
+        OverrideChildren,
 
-        Exact, // Layer needs the exact frame rate.
-
-        ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the
-                         // content properly. Any other value will result in a pull down.
-
-        NoVote, // Layer doesn't have any requirements for the refresh rate and
-                // should not be considered when the display refresh rate is determined.
-
-        ftl_last = NoVote
+        ftl_last = OverrideChildren
     };
 
-    // Encapsulates the frame rate and compatibility of the layer. This information will be used
+    // Encapsulates the frame rate specifications of the layer. This information will be used
     // when the display refresh rate is determined.
     struct FrameRate {
         using Seamlessness = scheduler::Seamlessness;
 
-        Fps rate;
-        FrameRateCompatibility type = FrameRateCompatibility::Default;
-        Seamlessness seamlessness = Seamlessness::Default;
+        // Information related to a specific desired frame rate vote.
+        struct FrameRateVote {
+            Fps rate;
+            FrameRateCompatibility type = FrameRateCompatibility::Default;
+            Seamlessness seamlessness = Seamlessness::Default;
+
+            bool operator==(const FrameRateVote& other) const {
+                return isApproxEqual(rate, other.rate) && type == other.type &&
+                        seamlessness == other.seamlessness;
+            }
+
+            FrameRateVote() = default;
+
+            FrameRateVote(Fps rate, FrameRateCompatibility type,
+                          Seamlessness seamlessness = Seamlessness::OnlySeamless)
+                  : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
+        } vote;
+
+        FrameRateCategory category = FrameRateCategory::Default;
+        bool categorySmoothSwitchOnly = false;
 
         FrameRate() = default;
 
         FrameRate(Fps rate, FrameRateCompatibility type,
-                  Seamlessness seamlessness = Seamlessness::OnlySeamless)
-              : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
+                  Seamlessness seamlessness = Seamlessness::OnlySeamless,
+                  FrameRateCategory category = FrameRateCategory::Default)
+              : vote(FrameRateVote(rate, type, seamlessness)), category(category) {}
 
         bool operator==(const FrameRate& other) const {
-            return isApproxEqual(rate, other.rate) && type == other.type &&
-                    seamlessness == other.seamlessness;
+            return vote == other.vote && category == other.category;
         }
 
         bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -113,8 +129,22 @@
         // Convert an ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* value to a
         // Layer::FrameRateCompatibility. Logs fatal if the compatibility value is invalid.
         static FrameRateCompatibility convertCompatibility(int8_t compatibility);
+
+        // Convert an ANATIVEWINDOW_CHANGE_FRAME_RATE_* value to a scheduler::Seamlessness.
+        // Logs fatal if the strategy value is invalid.
         static scheduler::Seamlessness convertChangeFrameRateStrategy(int8_t strategy);
 
+        // Convert an ANATIVEWINDOW_FRAME_RATE_CATEGORY_* value to a FrameRateCategory.
+        // Logs fatal if the category value is invalid.
+        static FrameRateCategory convertCategory(int8_t category);
+
+        // True if the FrameRate has explicit frame rate specifications.
+        bool isValid() const;
+
+        // Returns true if the FrameRate explicitly instructs to not contribute to frame rate
+        // selection.
+        bool isNoVote() const;
+
     private:
         static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
             if (!rate.isValid()) {
@@ -126,6 +156,10 @@
         }
     };
 
+    // Convert an ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_* value to FrameRateSelectionStrategy.
+    // Logs fatal if the strategy value is invalid.
+    static FrameRateSelectionStrategy convertFrameRateSelectionStrategy(int8_t strategy);
+
     static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
 
     LayerInfo(const std::string& name, uid_t ownerUid, LayerHistory::LayerVoteType defaultVote);
@@ -149,13 +183,15 @@
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
     // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default}; }
+    void resetLayerVote() {
+        mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default, FrameRateCategory::Default};
+    }
 
     std::string getName() const { return mName; }
 
     uid_t getOwnerUid() const { return mOwnerUid; }
 
-    LayerVote getRefreshRateVote(const RefreshRateSelector&, nsecs_t now);
+    RefreshRateVotes getRefreshRateVote(const RefreshRateSelector&, nsecs_t now);
 
     // Return the last updated time. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
@@ -230,6 +266,7 @@
 
     private:
         friend class LayerHistoryTest;
+        friend class LayerHistoryIntegrationTest;
 
         // Holds the refresh rate when it was calculated
         struct RefreshRateData {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 6b7d7df..1d23fb5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -38,6 +38,9 @@
 
 #include "../SurfaceFlingerProperties.h"
 #include "RefreshRateSelector.h"
+#include "Utils/FlagUtils.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
 
 #undef LOG_TAG
 #define LOG_TAG "RefreshRateSelector"
@@ -45,6 +48,8 @@
 namespace android::scheduler {
 namespace {
 
+using namespace com::android::graphics::surfaceflinger;
+
 struct RefreshRateScore {
     FrameRateMode frameRateMode;
     float overallScore;
@@ -69,7 +74,7 @@
 
     // Add all supported refresh rates.
     for (const auto& [id, mode] : modes) {
-        knownFrameRates.push_back(mode->getFps());
+        knownFrameRates.push_back(mode->getPeakFps());
     }
 
     // Sort and remove duplicates.
@@ -91,17 +96,17 @@
         const auto& mode1 = it1->second;
         const auto& mode2 = it2->second;
 
-        if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) {
+        if (mode1->getVsyncRate().getPeriodNsecs() == mode2->getVsyncRate().getPeriodNsecs()) {
             return mode1->getGroup() > mode2->getGroup();
         }
 
-        return mode1->getVsyncPeriod() > mode2->getVsyncPeriod();
+        return mode1->getVsyncRate().getPeriodNsecs() > mode2->getVsyncRate().getPeriodNsecs();
     });
 
     return sortedModes;
 }
 
-std::pair<unsigned, unsigned> divisorRange(Fps fps, FpsRange range,
+std::pair<unsigned, unsigned> divisorRange(Fps vsyncRate, Fps peakFps, FpsRange range,
                                            RefreshRateSelector::Config::FrameRateOverride config) {
     if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) {
         return {1, 1};
@@ -109,8 +114,14 @@
 
     using fps_approx_ops::operator/;
     // use signed type as `fps / range.max` might be 0
-    const auto start = std::max(1, static_cast<int>(fps / range.max) - 1);
-    const auto end = fps /
+    auto start = std::max(1, static_cast<int>(peakFps / range.max) - 1);
+    if (flagutils::vrrConfigEnabled()) {
+        start = std::max(1,
+                         static_cast<int>(vsyncRate /
+                                          std::min(range.max, peakFps, fps_approx_ops::operator<)) -
+                                 1);
+    }
+    const auto end = vsyncRate /
             std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
                      fps_approx_ops::operator<);
 
@@ -123,7 +134,8 @@
         for (const auto it2 : sortedModes) {
             const auto& mode2 = it2->second;
 
-            if (RefreshRateSelector::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
+            if (RefreshRateSelector::getFrameRateDivisor(mode1->getPeakFps(),
+                                                         mode2->getPeakFps()) >= 2) {
                 return true;
             }
         }
@@ -176,10 +188,12 @@
         if (!filterModes(*mode)) {
             continue;
         }
+        const auto vsyncRate = mode->getVsyncRate();
+        const auto peakFps = mode->getPeakFps();
         const auto [start, end] =
-                divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride);
+                divisorRange(vsyncRate, peakFps, renderRange, mConfig.enableFrameRateOverride);
         for (auto divisor = start; divisor <= end; divisor++) {
-            const auto fps = mode->getFps() / divisor;
+            const auto fps = vsyncRate / divisor;
             using fps_approx_ops::operator<;
             if (divisor > 1 && fps < kMinSupportedFrameRate) {
                 break;
@@ -199,28 +213,30 @@
             const auto [existingIter, emplaceHappened] =
                     ratesMap.try_emplace(Key{fps, mode->getGroup()}, it);
             if (emplaceHappened) {
-                ALOGV("%s: including %s (%s)", __func__, to_string(fps).c_str(),
-                      to_string(mode->getFps()).c_str());
+                ALOGV("%s: including %s (%s(%s))", __func__, to_string(fps).c_str(),
+                      to_string(peakFps).c_str(), to_string(vsyncRate).c_str());
             } else {
                 // If the primary physical range is a single rate, prefer to stay in that rate
                 // even if there is a lower physical refresh rate available. This would cause more
                 // cases to stay within the primary physical range
-                const Fps existingModeFps = existingIter->second->second->getFps();
+                const Fps existingModeFps = existingIter->second->second->getPeakFps();
                 const bool existingModeIsPrimaryRange = policy.primaryRangeIsSingleRate() &&
                         policy.primaryRanges.physical.includes(existingModeFps);
                 const bool newModeIsPrimaryRange = policy.primaryRangeIsSingleRate() &&
-                        policy.primaryRanges.physical.includes(mode->getFps());
+                        policy.primaryRanges.physical.includes(mode->getPeakFps());
                 if (newModeIsPrimaryRange == existingModeIsPrimaryRange) {
                     // We might need to update the map as we found a lower refresh rate
-                    if (isStrictlyLess(mode->getFps(), existingModeFps)) {
+                    if (isStrictlyLess(mode->getPeakFps(), existingModeFps)) {
                         existingIter->second = it;
-                        ALOGV("%s: changing %s (%s) as we found a lower physical rate", __func__,
-                              to_string(fps).c_str(), to_string(mode->getFps()).c_str());
+                        ALOGV("%s: changing %s (%s(%s)) as we found a lower physical rate",
+                              __func__, to_string(fps).c_str(), to_string(peakFps).c_str(),
+                              to_string(vsyncRate).c_str());
                     }
                 } else if (newModeIsPrimaryRange) {
                     existingIter->second = it;
-                    ALOGV("%s: changing %s (%s) to stay in the primary range", __func__,
-                          to_string(fps).c_str(), to_string(mode->getFps()).c_str());
+                    ALOGV("%s: changing %s (%s(%s)) to stay in the primary range", __func__,
+                          to_string(fps).c_str(), to_string(peakFps).c_str(),
+                          to_string(vsyncRate).c_str());
                 }
             }
         }
@@ -237,8 +253,8 @@
     const auto lowestRefreshRateIt =
             std::min_element(frameRateModes.begin(), frameRateModes.end(),
                              [](const FrameRateMode& lhs, const FrameRateMode& rhs) {
-                                 return isStrictlyLess(lhs.modePtr->getFps(),
-                                                       rhs.modePtr->getFps());
+                                 return isStrictlyLess(lhs.modePtr->getVsyncRate(),
+                                                       rhs.modePtr->getVsyncRate());
                              });
     frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt);
 
@@ -289,6 +305,25 @@
     return {quotient, remainder};
 }
 
+float RefreshRateSelector::calculateNonExactMatchingDefaultLayerScoreLocked(
+        nsecs_t displayPeriod, nsecs_t layerPeriod) const {
+    // Find the actual rate the layer will render, assuming
+    // that layerPeriod is the minimal period to render a frame.
+    // For example if layerPeriod is 20ms and displayPeriod is 16ms,
+    // then the actualLayerPeriod will be 32ms, because it is the
+    // smallest multiple of the display period which is >= layerPeriod.
+    auto actualLayerPeriod = displayPeriod;
+    int multiplier = 1;
+    while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
+        multiplier++;
+        actualLayerPeriod = displayPeriod * multiplier;
+    }
+
+    // Because of the threshold we used above it's possible that score is slightly
+    // above 1.
+    return std::min(1.0f, static_cast<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
+}
+
 float RefreshRateSelector::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
                                                                      Fps refreshRate) const {
     constexpr float kScoreForFractionalPairs = .8f;
@@ -296,22 +331,7 @@
     const auto displayPeriod = refreshRate.getPeriodNsecs();
     const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
     if (layer.vote == LayerVoteType::ExplicitDefault) {
-        // Find the actual rate the layer will render, assuming
-        // that layerPeriod is the minimal period to render a frame.
-        // For example if layerPeriod is 20ms and displayPeriod is 16ms,
-        // then the actualLayerPeriod will be 32ms, because it is the
-        // smallest multiple of the display period which is >= layerPeriod.
-        auto actualLayerPeriod = displayPeriod;
-        int multiplier = 1;
-        while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
-            multiplier++;
-            actualLayerPeriod = displayPeriod * multiplier;
-        }
-
-        // Because of the threshold we used above it's possible that score is slightly
-        // above 1.
-        return std::min(1.0f,
-                        static_cast<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
+        return calculateNonExactMatchingDefaultLayerScoreLocked(displayPeriod, layerPeriod);
     }
 
     if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
@@ -386,6 +406,22 @@
     constexpr float kSeamedSwitchPenalty = 0.95f;
     const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
 
+    if (layer.vote == LayerVoteType::ExplicitCategory) {
+        if (getFrameRateCategoryRange(layer.frameRateCategory).includes(refreshRate)) {
+            return 1.f;
+        }
+
+        FpsRange categoryRange = getFrameRateCategoryRange(layer.frameRateCategory);
+        using fps_approx_ops::operator<;
+        if (refreshRate < categoryRange.min) {
+            return calculateNonExactMatchingDefaultLayerScoreLocked(refreshRate.getPeriodNsecs(),
+                                                                    categoryRange.min
+                                                                            .getPeriodNsecs());
+        }
+        return calculateNonExactMatchingDefaultLayerScoreLocked(refreshRate.getPeriodNsecs(),
+                                                                categoryRange.max.getPeriodNsecs());
+    }
+
     // If the layer wants Max, give higher score to the higher refresh rate
     if (layer.vote == LayerVoteType::Max) {
         return calculateDistanceScoreFromMax(refreshRate);
@@ -405,7 +441,8 @@
 
     // If the layer frame rate is a divisor of the refresh rate it should score
     // the highest score.
-    if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
+    if (layer.desiredRefreshRate.isValid() &&
+        getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
         return 1.0f * seamlessness;
     }
 
@@ -455,7 +492,9 @@
     int explicitDefaultVoteLayers = 0;
     int explicitExactOrMultipleVoteLayers = 0;
     int explicitExact = 0;
+    int explicitCategoryVoteLayers = 0;
     int seamedFocusedLayers = 0;
+    int categorySmoothSwitchOnlyLayers = 0;
 
     for (const auto& layer : layers) {
         switch (layer.vote) {
@@ -477,6 +516,15 @@
             case LayerVoteType::ExplicitExact:
                 explicitExact++;
                 break;
+            case LayerVoteType::ExplicitCategory:
+                explicitCategoryVoteLayers++;
+                if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
+                    // Count this layer for Min vote as well. The explicit vote avoids
+                    // touch boost and idle for choosing a category, while Min vote is for correct
+                    // behavior when all layers are Min or no vote.
+                    minVoteLayers++;
+                }
+                break;
             case LayerVoteType::Heuristic:
                 break;
         }
@@ -484,10 +532,14 @@
         if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
             seamedFocusedLayers++;
         }
+        if (layer.frameRateCategorySmoothSwitchOnly) {
+            categorySmoothSwitchOnlyLayers++;
+        }
     }
 
     const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
-            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0;
+            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0 ||
+            explicitCategoryVoteLayers > 0;
 
     const Policy* policy = getCurrentPolicyLocked();
     const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
@@ -530,10 +582,17 @@
         return {ranking, kNoSignals};
     }
 
+    const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
+    const DisplayModeId activeModeId = activeMode.getId();
+
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("All layers Min");
-        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending,
+                                            std::nullopt, [&](FrameRateMode mode) {
+                                                return !smoothSwitchOnly ||
+                                                        mode.modePtr->getId() == activeModeId;
+                                            });
         ATRACE_FORMAT_INSTANT("%s (All layers Min)",
                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, kNoSignals};
@@ -548,10 +607,12 @@
     }
 
     for (const auto& layer : layers) {
-        ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(),
-              ftl::enum_string(layer.vote).c_str(), layer.weight,
-              layer.desiredRefreshRate.getValue());
-        if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
+        ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f, category %s) ",
+              layer.name.c_str(), ftl::enum_string(layer.vote).c_str(), layer.weight,
+              layer.desiredRefreshRate.getValue(),
+              ftl::enum_string(layer.frameRateCategory).c_str());
+        if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
+            layer.vote == LayerVoteType::Min) {
             continue;
         }
 
@@ -577,6 +638,14 @@
                 continue;
             }
 
+            if (smoothSwitchOnly && modePtr->getId() != activeModeId) {
+                ALOGV("%s ignores %s because it's non-VRR and smooth switch only."
+                      " Current mode = %s",
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
+                      to_string(activeMode).c_str());
+                continue;
+            }
+
             // Layers with default seamlessness vote for the current mode group if
             // there are layers with seamlessness=SeamedAndSeamless and for the default
             // mode group otherwise. In second case, if the current mode group is different
@@ -590,7 +659,7 @@
             }
 
             const bool inPrimaryPhysicalRange =
-                    policy->primaryRanges.physical.includes(modePtr->getFps());
+                    policy->primaryRanges.physical.includes(modePtr->getPeakFps());
             const bool inPrimaryRenderRange = policy->primaryRanges.render.includes(fps);
             if (((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
                  !inPrimaryRenderRange) &&
@@ -622,6 +691,7 @@
                     case LayerVoteType::Max:
                     case LayerVoteType::ExplicitDefault:
                     case LayerVoteType::ExplicitExact:
+                    case LayerVoteType::ExplicitCategory:
                         return false;
                 }
             }(layer.vote);
@@ -630,21 +700,24 @@
                             Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
             if (fixedSourceLayer && layerBelowThreshold) {
                 const bool modeAboveThreshold =
-                        modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+                        modePtr->getPeakFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
                 if (modeAboveThreshold) {
-                    ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f",
+                    ALOGV("%s gives %s (%s(%s)) fixed source (above threshold) score of %.4f",
                           formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
-                          to_string(modePtr->getFps()).c_str(), layerScore);
+                          to_string(modePtr->getPeakFps()).c_str(),
+                          to_string(modePtr->getVsyncRate()).c_str(), layerScore);
                     fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
                 } else {
-                    ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f",
+                    ALOGV("%s gives %s (%s(%s)) fixed source (below threshold) score of %.4f",
                           formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
-                          to_string(modePtr->getFps()).c_str(), layerScore);
+                          to_string(modePtr->getPeakFps()).c_str(),
+                          to_string(modePtr->getVsyncRate()).c_str(), layerScore);
                     fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
                 }
             } else {
-                ALOGV("%s gives %s (%s) score of %.4f", formatLayerInfo(layer, weight).c_str(),
-                      to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore);
+                ALOGV("%s gives %s (%s(%s)) score of %.4f", formatLayerInfo(layer, weight).c_str(),
+                      to_string(fps).c_str(), to_string(modePtr->getPeakFps()).c_str(),
+                      to_string(modePtr->getVsyncRate()).c_str(), layerScore);
                 overallScore += weightedLayerScore;
             }
         }
@@ -665,13 +738,14 @@
                                  [](RefreshRateScore max, RefreshRateScore current) {
                                      return current.overallScore > max.overallScore;
                                  });
-        ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the "
+        ALOGV("%s (%s(%s)) is the best refresh rate without fixed source layers. It is %s the "
               "threshold for "
               "refresh rate multiples",
               to_string(maxScoreIt->frameRateMode.fps).c_str(),
-              to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(),
+              to_string(maxScoreIt->frameRateMode.modePtr->getPeakFps()).c_str(),
+              to_string(maxScoreIt->frameRateMode.modePtr->getVsyncRate()).c_str(),
               maxScoreAboveThreshold ? "above" : "below");
-        return maxScoreIt->frameRateMode.modePtr->getFps() >=
+        return maxScoreIt->frameRateMode.modePtr->getPeakFps() >=
                 Fps::fromValue(mConfig.frameRateMultipleThreshold);
     }();
 
@@ -681,8 +755,9 @@
         if (maxScoreAboveThreshold) {
             overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
         }
-        ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
-              to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
+        ALOGV("%s (%s(%s)) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
+              to_string(frameRateMode.modePtr->getPeakFps()).c_str(),
+              to_string(frameRateMode.modePtr->getVsyncRate()).c_str(), overallScore);
     }
 
     // Now that we scored all the refresh rates we need to pick the one that got the highest
@@ -714,6 +789,7 @@
                                   to_string(descending.front().frameRateMode.fps).c_str());
             return {descending, kNoSignals};
         } else {
+            ALOGV("primaryRangeIsSingleRate");
             ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
                                   to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
@@ -737,7 +813,8 @@
     const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
     using fps_approx_ops::operator<;
 
-    if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
+    if (signals.touch && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
+        touchBoostForExplicitExact &&
         scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
         ALOGV("Touch Boost");
         ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
@@ -748,6 +825,7 @@
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
     // current config
     if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
+        ALOGV("preferredDisplayMode");
         const auto ascendingWithPreferred =
                 rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
         ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
@@ -755,6 +833,7 @@
         return {ascendingWithPreferred, kNoSignals};
     }
 
+    ALOGV("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
     ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
@@ -807,6 +886,7 @@
 
     const auto* policyPtr = getCurrentPolicyLocked();
     // We don't want to run lower than 30fps
+    // TODO(b/297600226): revise this for dVRR
     const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
 
     using fps_approx_ops::operator/;
@@ -848,13 +928,16 @@
         }
 
         for (const auto& layer : layersWithSameUid) {
-            if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) {
+            if (layer->isNoVote() || layer->frameRateCategory == FrameRateCategory::NoPreference ||
+                layer->vote == LayerVoteType::Min) {
                 continue;
             }
 
             LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
-                                layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
-                                layer->vote != LayerVoteType::ExplicitExact);
+                                        layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
+                                        layer->vote != LayerVoteType::ExplicitExact &&
+                                        layer->vote != LayerVoteType::ExplicitCategory,
+                                "Invalid layer vote type for frame rate overrides");
             for (auto& [fps, score] : scoredFrameRates) {
                 constexpr bool isSeamlessSwitch = true;
                 const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch);
@@ -881,6 +964,8 @@
                                       return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
                                   });
         ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
+        ATRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
+                              to_string(overrideFps).c_str(), uid);
         frameRateOverrides.emplace(uid, overrideFps);
     }
 
@@ -894,7 +979,7 @@
     const auto current = [&]() REQUIRES(mLock) -> FrameRateMode {
         if (desiredActiveModeId) {
             const auto& modePtr = mDisplayModes.get(*desiredActiveModeId)->get();
-            return FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)};
+            return FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
         }
 
         return getActiveModeLocked();
@@ -905,7 +990,7 @@
         return {};
     }
 
-    return timerExpired ? FrameRateMode{min->getFps(), ftl::as_non_null(min)} : current;
+    return timerExpired ? FrameRateMode{min->getPeakFps(), ftl::as_non_null(min)} : current;
 }
 
 const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
@@ -931,12 +1016,12 @@
     bool maxByAnchorFound = false;
     for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
         using namespace fps_approx_ops;
-        if (it->modePtr->getFps() > (*max)->getFps()) {
+        if (it->modePtr->getPeakFps() > (*max)->getPeakFps()) {
             max = &it->modePtr;
         }
 
         if (anchorGroup == it->modePtr->getGroup() &&
-            it->modePtr->getFps() >= (*maxByAnchor)->getFps()) {
+            it->modePtr->getPeakFps() >= (*maxByAnchor)->getPeakFps()) {
             maxByAnchorFound = true;
             maxByAnchor = &it->modePtr;
         }
@@ -954,7 +1039,8 @@
 
 auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
                                          RefreshRateOrder refreshRateOrder,
-                                         std::optional<DisplayModeId> preferredDisplayModeOpt) const
+                                         std::optional<DisplayModeId> preferredDisplayModeOpt,
+                                         const RankFrameRatesPredicate& predicate) const
         -> FrameRateRanking {
     using fps_approx_ops::operator<;
     const char* const whence = __func__;
@@ -981,7 +1067,8 @@
     std::deque<ScoredFrameRate> ranking;
     const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
         const auto& modePtr = frameRateMode.modePtr;
-        if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
+        if ((anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) ||
+            !predicate(frameRateMode)) {
             return;
         }
 
@@ -1014,8 +1101,9 @@
             return;
         }
 
-        ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
-              to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score);
+        ALOGV("%s(%s) %s (%s(%s)) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
+              to_string(frameRateMode.fps).c_str(), to_string(modePtr->getPeakFps()).c_str(),
+              to_string(modePtr->getVsyncRate()).c_str(), score);
         ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
     };
 
@@ -1095,8 +1183,8 @@
     mDisplayModes = std::move(modes);
     const auto activeModeOpt = mDisplayModes.get(activeModeId);
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
-    mActiveModeOpt =
-            FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())};
+    mActiveModeOpt = FrameRateMode{activeModeOpt->get()->getPeakFps(),
+                                   ftl::as_non_null(activeModeOpt->get())};
 
     const auto sortedModes = sortByRefreshRate(mDisplayModes);
     mMinRefreshRateModeIt = sortedModes.front();
@@ -1122,7 +1210,7 @@
     if (mConfig.enableFrameRateOverride ==
         Config::FrameRateOverride::AppOverrideNativeRefreshRates) {
         for (const auto& [_, mode] : mDisplayModes) {
-            mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit);
+            mAppOverrideNativeRefreshRates.try_emplace(mode->getPeakFps(), ftl::unit);
         }
     }
 
@@ -1132,7 +1220,7 @@
 bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const {
     // defaultMode must be a valid mode, and within the given refresh rate range.
     if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
-        if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) {
+        if (!policy.primaryRanges.physical.includes(mode->get()->getPeakFps())) {
             ALOGE("Default mode is not in the primary range.");
             return false;
         }
@@ -1245,8 +1333,8 @@
             return mode.getResolution() == defaultMode->getResolution() &&
                     mode.getDpi() == defaultMode->getDpi() &&
                     (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
-                    ranges.physical.includes(mode.getFps()) &&
-                    (supportsFrameRateOverride() || ranges.render.includes(mode.getFps()));
+                    ranges.physical.includes(mode.getPeakFps()) &&
+                    (supportsFrameRateOverride() || ranges.render.includes(mode.getPeakFps()));
         };
 
         auto frameRateModes = createFrameRateModes(*policy, filterModes, ranges.render);
@@ -1301,13 +1389,13 @@
 auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction {
     std::lock_guard lock(mLock);
 
-    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps();
+    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getPeakFps();
     const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked();
 
     // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that
     // the min allowed refresh rate is higher than the device min, we do not want to enable the
     // timer.
-    if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
+    if (isStrictlyLess(deviceMinFps, minByPolicy->getPeakFps())) {
         return KernelIdleTimerAction::TurnOff;
     }
 
@@ -1393,6 +1481,27 @@
     return mConfig.idleTimerTimeout;
 }
 
+// TODO(b/293651105): Extract category FpsRange mapping to OEM-configurable config.
+FpsRange RefreshRateSelector::getFrameRateCategoryRange(FrameRateCategory category) {
+    switch (category) {
+        case FrameRateCategory::High:
+            return FpsRange{90_Hz, 120_Hz};
+        case FrameRateCategory::Normal:
+            return FpsRange{60_Hz, 90_Hz};
+        case FrameRateCategory::Low:
+            return FpsRange{30_Hz, 60_Hz};
+        case FrameRateCategory::NoPreference:
+        case FrameRateCategory::Default:
+            LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
+                             ftl::enum_string(category).c_str());
+            return FpsRange{0_Hz, 0_Hz};
+        default:
+            LOG_ALWAYS_FATAL("Invalid frame rate category for range: %s",
+                             ftl::enum_string(category).c_str());
+            return FpsRange{0_Hz, 0_Hz};
+    }
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index b25919e..545b939 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -152,8 +152,9 @@
                                  // ExactOrMultiple compatibility
         ExplicitExact,           // Specific refresh rate that was provided by the app with
                                  // Exact compatibility
+        ExplicitCategory,        // Specific frame rate category was provided by the app
 
-        ftl_last = ExplicitExact
+        ftl_last = ExplicitCategory
     };
 
     // Captures the layer requirements for a refresh rate. This will be used to determine the
@@ -169,6 +170,11 @@
         Fps desiredRefreshRate;
         // If a seamless mode switch is required.
         Seamlessness seamlessness = Seamlessness::Default;
+        // Layer frame rate category.
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        // Goes together with frame rate category vote. Allow refresh rate changes only
+        // if there would be no jank.
+        bool frameRateCategorySmoothSwitchOnly = false;
         // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
         // would have on choosing the refresh rate.
         float weight = 0.0f;
@@ -179,12 +185,18 @@
             return name == other.name && vote == other.vote &&
                     isApproxEqual(desiredRefreshRate, other.desiredRefreshRate) &&
                     seamlessness == other.seamlessness && weight == other.weight &&
-                    focused == other.focused;
+                    focused == other.focused && frameRateCategory == other.frameRateCategory;
         }
 
         bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
+
+        bool isNoVote() const { return RefreshRateSelector::isNoVote(vote); }
     };
 
+    // Returns true if the layer explicitly instructs to not contribute to refresh rate selection.
+    // In other words, true if the layer should be ignored.
+    static bool isNoVote(LayerVoteType vote) { return vote == LayerVoteType::NoVote; }
+
     // Global state describing signals that affect refresh rate choice.
     struct GlobalSignals {
         // Whether the user touched the screen recently. Used to apply touch boost.
@@ -242,7 +254,8 @@
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()};
+        return {mMinRefreshRateModeIt->second->getPeakFps(),
+                mMaxRefreshRateModeIt->second->getPeakFps()};
     }
 
     ftl::Optional<FrameRateMode> onKernelTimerChanged(
@@ -349,6 +362,9 @@
                                                  Fps displayFrameRate, GlobalSignals) const
             EXCLUDES(mLock);
 
+    // Gets the FpsRange that the FrameRateCategory represents.
+    static FpsRange getFrameRateCategoryRange(FrameRateCategory category);
+
     std::optional<KernelIdleTimerController> kernelIdleTimerController() {
         return mConfig.kernelIdleTimerController;
     }
@@ -433,10 +449,15 @@
         ftl_last = Descending
     };
 
-    // Only uses the primary range, not the app request range.
+    typedef std::function<bool(const FrameRateMode)> RankFrameRatesPredicate;
+
+    // Rank the frame rates.
+    // Only modes in the primary range for which `predicate` is `true` will be scored.
+    // Does not use the app requested range.
     FrameRateRanking rankFrameRates(
             std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
-            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt) const
+            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt,
+            const RankFrameRatesPredicate& predicate = [](FrameRateMode) { return true; }) const
             REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
@@ -452,6 +473,11 @@
     float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const
             REQUIRES(mLock);
 
+    // Calculates the score for non-exact matching layer that has LayerVoteType::ExplicitDefault.
+    float calculateNonExactMatchingDefaultLayerScoreLocked(nsecs_t displayPeriod,
+                                                           nsecs_t layerPeriod) const
+            REQUIRES(mLock);
+
     void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock)
             REQUIRES(kMainThreadContext);
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 68927bd..5b36a5e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -192,7 +192,8 @@
     for (const auto& [id, display] : mDisplays) {
         if (id == pacesetterId) continue;
 
-        const FrameTargeter& targeter = *display.targeterPtr;
+        FrameTargeter& targeter = *display.targeterPtr;
+        targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
         targets.try_emplace(id, &targeter.target());
     }
 
@@ -206,11 +207,20 @@
         if (id == pacesetterId) continue;
 
         FrameTargeter& targeter = *display.targeterPtr;
-        targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
-
         targeters.try_emplace(id, &targeter);
     }
 
+    if (flagutils::vrrConfigEnabled() &&
+        CC_UNLIKELY(mPacesetterFrameDurationFractionToSkip > 0.f)) {
+        const auto period = pacesetterTargeter.target().expectedFrameDuration();
+        const auto skipDuration = Duration::fromNs(
+                static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
+        ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
+                      mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
+        std::this_thread::sleep_for(skipDuration);
+        mPacesetterFrameDurationFractionToSkip = 0.f;
+    }
+
     const auto resultsPerDisplay = compositor.composite(pacesetterId, targeters);
     compositor.sample();
 
@@ -242,36 +252,35 @@
     return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate);
 }
 
-impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
-    return [this](nsecs_t expectedVsyncTime, uid_t uid) {
-        return !isVsyncValid(TimePoint::fromNs(expectedVsyncTime), uid);
-    };
+bool Scheduler::throttleVsync(android::TimePoint expectedPresentTime, uid_t uid) {
+    return !isVsyncValid(expectedPresentTime, uid);
 }
 
-impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
-    return [this](uid_t uid) {
-        const auto [refreshRate, period] = [this] {
-            std::scoped_lock lock(mDisplayLock);
-            const auto pacesetterOpt = pacesetterDisplayLocked();
-            LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
-            const Display& pacesetter = *pacesetterOpt;
-            return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
-                                  pacesetter.schedulePtr->period());
-        }();
+Period Scheduler::getVsyncPeriod(uid_t uid) {
+    const auto [refreshRate, period] = [this] {
+        std::scoped_lock lock(mDisplayLock);
+        const auto pacesetterOpt = pacesetterDisplayLocked();
+        LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
+        const Display& pacesetter = *pacesetterOpt;
+        return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
+                              pacesetter.schedulePtr->period());
+    }();
 
-        const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
+    const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
 
-        const auto frameRate = getFrameRateOverride(uid);
-        if (!frameRate.has_value()) {
-            return currentPeriod.ns();
-        }
+    const auto frameRate = getFrameRateOverride(uid);
+    if (!frameRate.has_value()) {
+        return currentPeriod;
+    }
 
-        const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
-        if (divisor <= 1) {
-            return currentPeriod.ns();
-        }
-        return currentPeriod.ns() * divisor;
-    };
+    const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
+    if (divisor <= 1) {
+        return currentPeriod;
+    }
+
+    // TODO(b/299378819): the casting is not needed, but we need a flag as it might change
+    // behaviour.
+    return Period::fromNs(currentPeriod.ns() * divisor);
 }
 
 ConnectionHandle Scheduler::createEventThread(Cycle cycle,
@@ -279,9 +288,7 @@
                                               std::chrono::nanoseconds workDuration,
                                               std::chrono::nanoseconds readyDuration) {
     auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
-                                                           getVsyncSchedule(), tokenManager,
-                                                           makeThrottleVsyncCallback(),
-                                                           makeGetVsyncPeriodFunction(),
+                                                           getVsyncSchedule(), tokenManager, *this,
                                                            workDuration, readyDuration);
 
     auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle;
@@ -293,29 +300,39 @@
     const ConnectionHandle handle = ConnectionHandle{mNextConnectionHandleId++};
     ALOGV("Creating a connection handle with ID %" PRIuPTR, handle.id);
 
-    auto connection = createConnectionInternal(eventThread.get());
+    auto connection = eventThread->createEventConnection();
 
     std::lock_guard<std::mutex> lock(mConnectionsLock);
     mConnections.emplace(handle, Connection{connection, std::move(eventThread)});
     return handle;
 }
 
-sp<EventThreadConnection> Scheduler::createConnectionInternal(
-        EventThread* eventThread, EventRegistrationFlags eventRegistration,
-        const sp<IBinder>& layerHandle) {
-    int32_t layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
-    auto connection = eventThread->createEventConnection([&] { resync(); }, eventRegistration);
-    mLayerHistory.attachChoreographer(layerId, connection);
-    return connection;
-}
-
 sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
         ConnectionHandle handle, EventRegistrationFlags eventRegistration,
         const sp<IBinder>& layerHandle) {
-    std::lock_guard<std::mutex> lock(mConnectionsLock);
-    RETURN_IF_INVALID_HANDLE(handle, nullptr);
-    return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration,
-                                    layerHandle);
+    const auto connection = [&]() -> sp<EventThreadConnection> {
+        std::scoped_lock lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle, nullptr);
+
+        return mConnections[handle].thread->createEventConnection(eventRegistration);
+    }();
+    const auto layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
+
+    if (layerId != static_cast<int32_t>(UNASSIGNED_LAYER_ID)) {
+        // TODO(b/290409668): Moving the choreographer attachment to be a transaction that will be
+        // processed on the main thread.
+        mSchedulerCallback.onChoreographerAttached();
+
+        std::scoped_lock lock(mChoreographerLock);
+        const auto [iter, emplaced] =
+                mAttachedChoreographers.emplace(layerId,
+                                                AttachedChoreographers{Fps(), {connection}});
+        if (!emplaced) {
+            iter->second.connections.emplace(connection);
+            connection->frameRate = iter->second.frameRate;
+        }
+    }
+    return connection;
 }
 
 sp<EventThreadConnection> Scheduler::getEventConnection(ConnectionHandle handle) {
@@ -336,6 +353,17 @@
     thread->onHotplugReceived(displayId, connected);
 }
 
+void Scheduler::onHotplugConnectionError(ConnectionHandle handle, int32_t errorCode) {
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+
+    thread->onHotplugConnectionError(errorCode);
+}
+
 void Scheduler::enableSyntheticVsync(bool enable) {
     // TODO(b/241285945): Remove connection handles.
     const ConnectionHandle handle = mAppConnectionHandle;
@@ -415,12 +443,6 @@
     thread->onModeChanged(mode);
 }
 
-size_t Scheduler::getEventThreadConnectionCount(ConnectionHandle handle) {
-    std::lock_guard<std::mutex> lock(mConnectionsLock);
-    RETURN_IF_INVALID_HANDLE(handle, 0);
-    return mConnections[handle].thread->getEventThreadConnectionCount();
-}
-
 void Scheduler::dump(ConnectionHandle handle, std::string& result) const {
     android::EventThread* thread;
     {
@@ -489,7 +511,7 @@
 
     if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) {
         if (!refreshRate) {
-            refreshRate = display.selectorPtr->getActiveMode().modePtr->getFps();
+            refreshRate = display.selectorPtr->getActiveMode().modePtr->getVsyncRate();
         }
         if (refreshRate->isValid()) {
             constexpr bool kForce = false;
@@ -537,7 +559,7 @@
                         to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value);
 
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
-          to_string(mode.modePtr->getFps()).c_str());
+          to_string(mode.modePtr->getVsyncRate()).c_str());
 
     display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
 }
@@ -596,10 +618,15 @@
     mLayerHistory.deregisterLayer(layer);
 }
 
+void Scheduler::onLayerDestroyed(Layer* layer) {
+    std::scoped_lock lock(mChoreographerLock);
+    mAttachedChoreographers.erase(layer->getSequence());
+}
+
 void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
-                                   LayerHistory::LayerUpdateType updateType) {
+                                   nsecs_t now, LayerHistory::LayerUpdateType updateType) {
     if (pacesetterSelectorPtr()->canSwitch()) {
-        mLayerHistory.record(id, layerProps, presentTime, systemTime(), updateType);
+        mLayerHistory.record(id, layerProps, presentTime, now, updateType);
     }
 }
 
@@ -607,12 +634,15 @@
     mLayerHistory.setModeChangePending(pending);
 }
 
-void Scheduler::setDefaultFrameRateCompatibility(Layer* layer) {
-    mLayerHistory.setDefaultFrameRateCompatibility(layer,
+void Scheduler::setDefaultFrameRateCompatibility(
+        int32_t id, scheduler::FrameRateCompatibility frameRateCompatibility) {
+    mLayerHistory.setDefaultFrameRateCompatibility(id, frameRateCompatibility,
                                                    mFeatures.test(Feature::kContentDetection));
 }
 
-void Scheduler::chooseRefreshRateForContent() {
+void Scheduler::chooseRefreshRateForContent(
+        const surfaceflinger::frontend::LayerHierarchy* hierarchy,
+        bool updateAttachedChoreographer) {
     const auto selectorPtr = pacesetterSelectorPtr();
     if (!selectorPtr->canSwitch()) return;
 
@@ -620,6 +650,20 @@
 
     LayerHistory::Summary summary = mLayerHistory.summarize(*selectorPtr, systemTime());
     applyPolicy(&Policy::contentRequirements, std::move(summary));
+
+    if (updateAttachedChoreographer) {
+        LOG_ALWAYS_FATAL_IF(!hierarchy);
+
+        // update the attached choreographers after we selected the render rate.
+        const ftl::Optional<FrameRateMode> modeOpt = [&] {
+            std::scoped_lock lock(mPolicyLock);
+            return mPolicy.modeOpt;
+        }();
+
+        if (modeOpt) {
+            updateAttachedChoreographers(*hierarchy, modeOpt->fps);
+        }
+    }
 }
 
 void Scheduler::resetIdleTimer() {
@@ -691,7 +735,7 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getFps();
+    const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getPeakFps();
 
     constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
     using namespace fps_approx_ops;
@@ -802,6 +846,12 @@
 }
 
 bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
+    std::scoped_lock lock(mPolicyLock);
+    return updateFrameRateOverridesLocked(consideredSignals, displayRefreshRate);
+}
+
+bool Scheduler::updateFrameRateOverridesLocked(GlobalSignals consideredSignals,
+                                               Fps displayRefreshRate) {
     if (consideredSignals.idle) return false;
 
     const auto frameRateOverrides =
@@ -845,7 +895,7 @@
 
         newVsyncSchedulePtr = pacesetter.schedulePtr;
 
-        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getFps();
+        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getVsyncRate();
         constexpr bool kForce = true;
         newVsyncSchedulePtr->startPeriodTransition(refreshRate.getPeriod(), kForce);
     }
@@ -879,6 +929,105 @@
     mPolicy = {};
 }
 
+void Scheduler::updateAttachedChoreographersFrameRate(
+        const surfaceflinger::frontend::RequestedLayerState& layer, Fps fps) {
+    std::scoped_lock lock(mChoreographerLock);
+
+    const auto layerId = static_cast<int32_t>(layer.id);
+    const auto choreographers = mAttachedChoreographers.find(layerId);
+    if (choreographers == mAttachedChoreographers.end()) {
+        return;
+    }
+
+    auto& layerChoreographers = choreographers->second;
+
+    layerChoreographers.frameRate = fps;
+    ATRACE_FORMAT_INSTANT("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
+    ALOGV("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
+
+    auto it = layerChoreographers.connections.begin();
+    while (it != layerChoreographers.connections.end()) {
+        sp<EventThreadConnection> choreographerConnection = it->promote();
+        if (choreographerConnection) {
+            choreographerConnection->frameRate = fps;
+            it++;
+        } else {
+            it = choreographers->second.connections.erase(it);
+        }
+    }
+
+    if (layerChoreographers.connections.empty()) {
+        mAttachedChoreographers.erase(choreographers);
+    }
+}
+
+int Scheduler::updateAttachedChoreographersInternal(
+        const surfaceflinger::frontend::LayerHierarchy& layerHierarchy, Fps displayRefreshRate,
+        int parentDivisor) {
+    const char* name = layerHierarchy.getLayer() ? layerHierarchy.getLayer()->name.c_str() : "Root";
+
+    int divisor = 0;
+    if (layerHierarchy.getLayer()) {
+        const auto frameRateCompatibility = layerHierarchy.getLayer()->frameRateCompatibility;
+        const auto frameRate = Fps::fromValue(layerHierarchy.getLayer()->frameRate);
+        ALOGV("%s: %s frameRate %s parentDivisor=%d", __func__, name, to_string(frameRate).c_str(),
+              parentDivisor);
+
+        if (frameRate.isValid()) {
+            if (frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE ||
+                frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_EXACT) {
+                // Since this layer wants an exact match, we would only set a frame rate if the
+                // desired rate is a divisor of the display refresh rate.
+                divisor = RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate);
+            } else if (frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT) {
+                // find the closest frame rate divisor for the desired frame rate.
+                divisor = static_cast<int>(
+                        std::round(displayRefreshRate.getValue() / frameRate.getValue()));
+            }
+        }
+    }
+
+    // We start by traversing the children, updating their choreographers, and getting back the
+    // aggregated frame rate.
+    int childrenDivisor = 0;
+    for (const auto& [child, _] : layerHierarchy.mChildren) {
+        LOG_ALWAYS_FATAL_IF(child == nullptr || child->getLayer() == nullptr);
+
+        ALOGV("%s: %s traversing child %s", __func__, name, child->getLayer()->name.c_str());
+
+        const int childDivisor =
+                updateAttachedChoreographersInternal(*child, displayRefreshRate, divisor);
+        childrenDivisor = childrenDivisor > 0 ? childrenDivisor : childDivisor;
+        if (childDivisor > 0) {
+            childrenDivisor = std::gcd(childrenDivisor, childDivisor);
+        }
+        ALOGV("%s: %s childrenDivisor=%d", __func__, name, childrenDivisor);
+    }
+
+    ALOGV("%s: %s divisor=%d", __func__, name, divisor);
+
+    // If there is no explicit vote for this layer. Use the children's vote if exists
+    divisor = (divisor == 0) ? childrenDivisor : divisor;
+    ALOGV("%s: %s divisor=%d with children", __func__, name, divisor);
+
+    // If there is no explicit vote for this layer or its children, Use the parent vote if exists
+    divisor = (divisor == 0) ? parentDivisor : divisor;
+    ALOGV("%s: %s divisor=%d with parent", __func__, name, divisor);
+
+    if (layerHierarchy.getLayer()) {
+        Fps fps = divisor > 1 ? displayRefreshRate / (unsigned int)divisor : Fps();
+        updateAttachedChoreographersFrameRate(*layerHierarchy.getLayer(), fps);
+    }
+
+    return divisor;
+}
+
+void Scheduler::updateAttachedChoreographers(
+        const surfaceflinger::frontend::LayerHierarchy& layerHierarchy, Fps displayRefreshRate) {
+    ATRACE_CALL();
+    updateAttachedChoreographersInternal(layerHierarchy, displayRefreshRate, 0);
+}
+
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
     ATRACE_CALL();
@@ -921,7 +1070,7 @@
                                                 .emitEvent = !choice.consideredSignals.idle});
         }
 
-        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modeOpt->fps);
+        frameRateOverridesChanged = updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
 
         if (mPolicy.modeOpt != modeOpt) {
             mPolicy.modeOpt = modeOpt;
@@ -1049,18 +1198,19 @@
 }
 
 void Scheduler::updateSmallAreaDetection(
-        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+        std::vector<std::pair<int32_t, float>>& uidThresholdMappings) {
     mSmallAreaDetectionAllowMappings.update(uidThresholdMappings);
 }
 
-void Scheduler::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
-    mSmallAreaDetectionAllowMappings.setThesholdForUid(uid, threshold);
+void Scheduler::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+    mSmallAreaDetectionAllowMappings.setThesholdForAppId(appId, threshold);
 }
 
-bool Scheduler::isSmallDirtyArea(uid_t uid, uint32_t dirtyArea) {
-    std::optional<float> oThreshold = mSmallAreaDetectionAllowMappings.getThresholdForUid(uid);
-    if (oThreshold) return mLayerHistory.isSmallDirtyArea(dirtyArea, oThreshold.value());
-
+bool Scheduler::isSmallDirtyArea(int32_t appId, uint32_t dirtyArea) {
+    std::optional<float> oThreshold = mSmallAreaDetectionAllowMappings.getThresholdForAppId(appId);
+    if (oThreshold) {
+        return mLayerHistory.isSmallDirtyArea(dirtyArea, oThreshold.value());
+    }
     return false;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 20cc77f..b0520a6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -53,6 +53,8 @@
 #include "Utils/Dumper.h"
 #include "VsyncModulator.h"
 
+#include <FrontEnd/LayerHierarchy.h>
+
 namespace android::scheduler {
 
 // Opaque handle to scheduler connection.
@@ -96,7 +98,7 @@
 
 class VsyncSchedule;
 
-class Scheduler : android::impl::MessageQueue {
+class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
 public:
@@ -154,11 +156,13 @@
 
     sp<IDisplayEventConnection> createDisplayEventConnection(
             ConnectionHandle, EventRegistrationFlags eventRegistration = {},
-            const sp<IBinder>& layerHandle = nullptr);
+            const sp<IBinder>& layerHandle = nullptr) EXCLUDES(mChoreographerLock);
 
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
+    void onHotplugConnectionError(ConnectionHandle, int32_t errorCode);
+
     void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock);
     void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&);
 
@@ -213,7 +217,6 @@
         ftl::FakeGuard guard(kMainThreadContext);
         resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate);
     }
-    void resync() EXCLUDES(mDisplayLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
     // Passes a vsync sample to VsyncController. Returns true if
@@ -227,13 +230,15 @@
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
     void recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
-                            LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
+                            nsecs_t now, LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
     void setModeChangePending(bool pending);
-    void setDefaultFrameRateCompatibility(Layer*);
+    void setDefaultFrameRateCompatibility(int32_t id, scheduler::FrameRateCompatibility);
     void deregisterLayer(Layer*);
+    void onLayerDestroyed(Layer*) EXCLUDES(mChoreographerLock);
 
     // Detects content using layer history, and selects a matching refresh rate.
-    void chooseRefreshRateForContent() EXCLUDES(mDisplayLock);
+    void chooseRefreshRateForContent(const surfaceflinger::frontend::LayerHierarchy*,
+                                     bool updateAttachedChoreographer) EXCLUDES(mDisplayLock);
 
     void resetIdleTimer();
 
@@ -282,20 +287,18 @@
     // Notifies the scheduler when the display size has changed. Called from SF's main thread
     void onActiveDisplayAreaChanged(uint32_t displayArea);
 
-    size_t getEventThreadConnectionCount(ConnectionHandle handle);
-
     // Stores the preferred refresh rate that an app should run at.
     // FrameRateOverride.refreshRateHz == 0 means no preference.
     void setPreferredRefreshRateForUid(FrameRateOverride);
 
     void setGameModeRefreshRateForUid(FrameRateOverride);
 
-    void updateSmallAreaDetection(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+    void updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
 
-    void setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+    void setSmallAreaDetectionThreshold(int32_t appId, float threshold);
 
     // Returns true if the dirty area is less than threshold.
-    bool isSmallDirtyArea(uid_t uid, uint32_t dirtyArea);
+    bool isSmallDirtyArea(int32_t appId, uint32_t dirtyArea);
 
     // Retrieves the overridden refresh rate for a given uid.
     std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock);
@@ -313,11 +316,18 @@
         return mLayerHistory.getLayerFramerate(now, id);
     }
 
+    bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) EXCLUDES(mPolicyLock);
+
     // Returns true if the small dirty detection is enabled.
     bool supportSmallDirtyDetection() const {
         return mFeatures.test(Feature::kSmallDirtyContentDetection);
     }
 
+    // Injects a delay that is a fraction of the predicted frame duration for the next frame.
+    void injectPacesetterDelay(float frameDurationFraction) REQUIRES(kMainThreadContext) {
+        mPacesetterFrameDurationFractionToSkip = frameDurationFraction;
+    }
+
 private:
     friend class TestableScheduler;
 
@@ -331,9 +341,6 @@
 
     // Create a connection on the given EventThread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread>);
-    sp<EventThreadConnection> createConnectionInternal(
-            EventThread*, EventRegistrationFlags eventRegistration = {},
-            const sp<IBinder>& layerHandle = nullptr);
 
     // Update feature state machine to given state when corresponding timer resets or expires.
     void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock);
@@ -406,12 +413,21 @@
 
     GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
 
-    bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock);
+    bool updateFrameRateOverridesLocked(GlobalSignals, Fps displayRefreshRate)
+            REQUIRES(mPolicyLock);
+    void updateAttachedChoreographers(const surfaceflinger::frontend::LayerHierarchy&,
+                                      Fps displayRefreshRate);
+    int updateAttachedChoreographersInternal(const surfaceflinger::frontend::LayerHierarchy&,
+                                             Fps displayRefreshRate, int parentDivisor);
+    void updateAttachedChoreographersFrameRate(const surfaceflinger::frontend::RequestedLayerState&,
+                                               Fps fps) EXCLUDES(mChoreographerLock);
 
     void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
 
-    android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const;
-    android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
+    // IEventThreadCallback overrides
+    bool throttleVsync(TimePoint, uid_t) override;
+    Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
+    void resync() override EXCLUDES(mDisplayLock);
 
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
@@ -441,6 +457,9 @@
     // Timer used to monitor display power mode.
     ftl::Optional<OneShotTimer> mDisplayPowerTimer;
 
+    // Injected delay prior to compositing, for simulating jank.
+    float mPacesetterFrameDurationFractionToSkip GUARDED_BY(kMainThreadContext) = 0.f;
+
     ISchedulerCallback& mSchedulerCallback;
 
     // mDisplayLock may be locked while under mPolicyLock.
@@ -536,6 +555,16 @@
         std::optional<ModeChangedParams> cachedModeChangedParams;
     } mPolicy GUARDED_BY(mPolicyLock);
 
+    std::mutex mChoreographerLock;
+
+    struct AttachedChoreographers {
+        Fps frameRate;
+        std::unordered_set<wp<EventThreadConnection>, WpHash> connections;
+    };
+    // Map keyed by layer ID (sequence) to choreographer connections.
+    std::unordered_map<int32_t, AttachedChoreographers> mAttachedChoreographers
+            GUARDED_BY(mChoreographerLock);
+
     std::mutex mVsyncTimelineLock;
     std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
             GUARDED_BY(mVsyncTimelineLock);
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
index 95cd5d1..38c6da4 100644
--- a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
@@ -19,26 +19,26 @@
 
 namespace android::scheduler {
 void SmallAreaDetectionAllowMappings::update(
-        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+        std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
     std::lock_guard lock(mLock);
     mMap.clear();
-    for (std::pair<uid_t, float> row : uidThresholdMappings) {
+    for (std::pair<int32_t, float> row : appIdThresholdMappings) {
         if (!isValidThreshold(row.second)) continue;
 
         mMap.emplace(row.first, row.second);
     }
 }
 
-void SmallAreaDetectionAllowMappings::setThesholdForUid(uid_t uid, float threshold) {
+void SmallAreaDetectionAllowMappings::setThesholdForAppId(int32_t appId, float threshold) {
     if (!isValidThreshold(threshold)) return;
 
     std::lock_guard lock(mLock);
-    mMap.emplace(uid, threshold);
+    mMap.emplace(appId, threshold);
 }
 
-std::optional<float> SmallAreaDetectionAllowMappings::getThresholdForUid(uid_t uid) {
+std::optional<float> SmallAreaDetectionAllowMappings::getThresholdForAppId(int32_t appId) {
     std::lock_guard lock(mLock);
-    const auto iter = mMap.find(uid);
+    const auto iter = mMap.find(appId);
     if (iter != mMap.end()) {
         return iter->second;
     }
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
index cbab690..e10301c 100644
--- a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
@@ -24,16 +24,16 @@
 
 namespace android::scheduler {
 class SmallAreaDetectionAllowMappings {
-    using UidThresholdMap = std::unordered_map<uid_t, float>;
+    using AppIdThresholdMap = std::unordered_map<int32_t, float>;
 
 public:
-    void update(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
-    void setThesholdForUid(uid_t uid, float threshold) EXCLUDES(mLock);
-    std::optional<float> getThresholdForUid(uid_t uid) EXCLUDES(mLock);
+    void update(std::vector<std::pair<int32_t, float>>& appIdThresholdMappings);
+    void setThesholdForAppId(int32_t appId, float threshold) EXCLUDES(mLock);
+    std::optional<float> getThresholdForAppId(int32_t uid) EXCLUDES(mLock);
 
 private:
     static bool isValidThreshold(float threshold) { return threshold >= 0.0f && threshold <= 1.0f; }
     mutable std::mutex mLock;
-    UidThresholdMap mMap GUARDED_BY(mLock);
+    AppIdThresholdMap mMap GUARDED_BY(mLock);
 };
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 1f922f1..c4c9fa5 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -28,10 +28,13 @@
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncTracker.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 #undef LOG_TAG
 #define LOG_TAG "VSyncDispatch"
 
 namespace android::scheduler {
+using namespace com::android::graphics::surfaceflinger;
 
 using base::StringAppendF;
 
@@ -100,8 +103,14 @@
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     bool const wouldSkipAWakeup =
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
-    if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
-        return getExpectedCallbackTime(nextVsyncTime, timing);
+    if (flags::dont_skip_on_early()) {
+        if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
+            return getExpectedCallbackTime(mArmedInfo->mActualVsyncTime, timing);
+        }
+    } else {
+        if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
+            return getExpectedCallbackTime(nextVsyncTime, timing);
+        }
     }
 
     nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime);
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 6499d69..e0fb8f9 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -146,13 +146,14 @@
     void cancelTimer() REQUIRES(mMutex);
     ScheduleResult scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex);
 
+    std::mutex mutable mMutex;
+
     static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
     std::unique_ptr<TimeKeeper> const mTimeKeeper;
     VsyncSchedule::TrackerPtr mTracker;
     nsecs_t const mTimerSlack;
     nsecs_t const mMinVsyncDistance;
 
-    std::mutex mutable mMutex;
     size_t mCallbackToken GUARDED_BY(mMutex) = 0;
 
     CallbackMap mCallbacks GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index a24e43f..b6cb373 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -143,7 +143,7 @@
  */
 class WorkDuration : public VsyncConfiguration {
 public:
-    explicit WorkDuration(Fps currentRefrshRate);
+    explicit WorkDuration(Fps currentRefreshRate);
 
 protected:
     // Used for unit tests
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 0757b57..47e92e1 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include <android-base/thread_annotations.h>
+#include <ThreadContext.h>
 #include <ftl/enum.h>
 #include <ftl/optional.h>
 #include <ui/DisplayId.h>
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index d6329e2..19e951a 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -23,6 +23,7 @@
 #include <type_traits>
 
 #include <android-base/stringprintf.h>
+#include <ftl/enum.h>
 #include <scheduler/Time.h>
 
 namespace android {
@@ -81,6 +82,17 @@
     bool valid() const;
 };
 
+// The frame rate category of a Layer.
+enum class FrameRateCategory {
+    Default,
+    NoPreference,
+    Low,
+    Normal,
+    High,
+
+    ftl_last = High
+};
+
 static_assert(std::is_trivially_copyable_v<Fps>);
 
 constexpr Fps operator""_Hz(unsigned long long frequency) {
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
index db38ebe..59a6df2 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -36,7 +36,8 @@
 };
 
 inline std::string to_string(const FrameRateMode& mode) {
-    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
+    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getPeakFps()) + "(" +
+            to_string(mode.modePtr->getVsyncRate()) + "))";
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Timer.h b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
index 58ad6cb..67f7abe 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
@@ -60,6 +60,7 @@
     void reset() EXCLUDES(mMutex);
     void cleanup() REQUIRES(mMutex);
     void setDebugState(DebugState) EXCLUDES(mMutex);
+    void setCallback(std::function<void()>&&) REQUIRES(mMutex);
 
     int mTimerFd = -1;
 
@@ -71,6 +72,7 @@
     void endDispatch();
 
     mutable std::mutex mMutex;
+
     std::function<void()> mCallback GUARDED_BY(mMutex);
     bool mExpectingCallback GUARDED_BY(mMutex) = false;
     DebugState mDebugState GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
index a4cf57f..09e8a1e 100644
--- a/services/surfaceflinger/Scheduler/src/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -93,8 +93,8 @@
         close(mPipes[kWritePipe]);
         mPipes[kWritePipe] = -1;
     }
-    mExpectingCallback = false;
-    mCallback = {};
+
+    setCallback({});
 }
 
 void Timer::endDispatch() {
@@ -112,8 +112,7 @@
     static constexpr int ns_per_s =
             std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
 
-    mCallback = std::move(callback);
-    mExpectingCallback = true;
+    setCallback(std::move(callback));
 
     struct itimerspec old_timer;
     struct itimerspec new_timer {
@@ -142,6 +141,8 @@
     if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) {
         ALOGW("Failed to disarm timerfd");
     }
+
+    setCallback({});
 }
 
 void Timer::threadMain() {
@@ -231,6 +232,11 @@
     mDebugState = state;
 }
 
+void Timer::setCallback(std::function<void()>&& callback) {
+    mExpectingCallback = bool(callback);
+    mCallback = std::move(callback);
+}
+
 void Timer::dump(std::string& result) const {
     std::lock_guard lock(mMutex);
     result.append("\t\tDebugState: ");
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4660d4c..9b9a67a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -24,6 +24,7 @@
 
 #include "SurfaceFlinger.h"
 
+#include <aidl/android/hardware/power/Boost.h>
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
@@ -34,7 +35,6 @@
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/types.h>
-#include <android/hardware/power/Boost.h>
 #include <android/native_window.h>
 #include <android/os/IInputFlinger.h>
 #include <binder/IPCThreadState.h>
@@ -68,6 +68,7 @@
 #include <gui/LayerMetadata.h>
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
 #include <gui/TraceUtils.h>
 #include <hidl/ServiceManagement.h>
 #include <layerproto/LayerProtoParser.h>
@@ -91,13 +92,12 @@
 #include <ui/LayerStack.h>
 #include <ui/PixelFormat.h>
 #include <ui/StaticDisplayInfo.h>
+#include <unistd.h>
 #include <utils/StopWatch.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
 #include <utils/Timers.h>
 #include <utils/misc.h>
-
-#include <unistd.h>
 #include <algorithm>
 #include <cerrno>
 #include <cinttypes>
@@ -113,6 +113,7 @@
 #include <vector>
 
 #include <gui/LayerStatePermissions.h>
+#include <gui/SchedulingPolicy.h>
 #include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
 #include "Client.h"
@@ -134,6 +135,7 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
 #include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerLog.h"
 #include "FrontEnd/LayerSnapshot.h"
 #include "HdrLayerInfoReporter.h"
 #include "Layer.h"
@@ -154,12 +156,15 @@
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
 #include "Utils/Dumper.h"
+#include "Utils/FlagUtils.h"
 #include "WindowInfosListenerInvoker.h"
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 #undef NO_THREAD_SAFETY_ANALYSIS
 #define NO_THREAD_SAFETY_ANALYSIS \
     _Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
@@ -169,6 +174,7 @@
 #define DOES_CONTAIN_BORDER false
 
 namespace android {
+using namespace com::android::graphics::surfaceflinger;
 
 using namespace std::chrono_literals;
 using namespace std::string_literals;
@@ -319,13 +325,12 @@
 
 const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
 
-static const int MAX_TRACING_MEMORY = 1024 * 1024 * 1024; // 1GB
-
 // ---------------------------------------------------------------------------
 int64_t SurfaceFlinger::dispSyncPresentTimeOffset;
 bool SurfaceFlinger::useHwcForRgbToYuv;
 bool SurfaceFlinger::hasSyncFramework;
 int64_t SurfaceFlinger::maxFrameBufferAcquiredBuffers;
+int64_t SurfaceFlinger::minAcquiredBuffers = 1;
 uint32_t SurfaceFlinger::maxGraphicsWidth;
 uint32_t SurfaceFlinger::maxGraphicsHeight;
 bool SurfaceFlinger::useContextPriority;
@@ -377,6 +382,7 @@
 }
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipInitialization) {
+    ATRACE_CALL();
     ALOGI("SurfaceFlinger is starting");
 
     hasSyncFramework = running_without_sync_framework(true);
@@ -386,6 +392,8 @@
     useHwcForRgbToYuv = force_hwc_copy_for_virtual_displays(false);
 
     maxFrameBufferAcquiredBuffers = max_frame_buffer_acquired_buffers(2);
+    minAcquiredBuffers =
+            SurfaceFlingerProperties::min_acquired_buffers().value_or(minAcquiredBuffers);
 
     maxGraphicsWidth = std::max(max_graphics_width(0), 0);
     maxGraphicsHeight = std::max(max_graphics_height(0), 0);
@@ -402,9 +410,6 @@
     wideColorGamutCompositionPixelFormat =
             static_cast<ui::PixelFormat>(wcg_composition_pixel_format(ui::PixelFormat::RGBA_8888));
 
-    mColorSpaceAgnosticDataspace =
-            static_cast<ui::Dataspace>(color_space_agnostic_dataspace(Dataspace::UNKNOWN));
-
     mLayerCachingEnabled = [] {
         const bool enable =
                 android::sysprop::SurfaceFlingerProperties::enable_layer_caching().value_or(false);
@@ -481,14 +486,20 @@
 
     if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) {
         mTransactionTracing.emplace();
+        mLayerTracing.setTransactionTracing(*mTransactionTracing);
     }
 
     mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
 
     mLayerLifecycleManagerEnabled =
-            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, false);
+            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
             base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
+
+    // Trunk-Stable flags
+    mMiscFlagValue = flags::misc1();
+    mConnectedDisplayFlagValue = flags::connected_display();
+    mMisc2FlagEarlyBootValue = flags::late_boot_misc2();
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -496,10 +507,6 @@
         return LatchUnsignaledConfig::AutoSingleLayer;
     }
 
-    if (base::GetBoolProperty("debug.sf.latch_unsignaled"s, false)) {
-        return LatchUnsignaledConfig::Always;
-    }
-
     return LatchUnsignaledConfig::Disabled;
 }
 
@@ -650,14 +657,6 @@
     return getPhysicalDisplayTokenLocked(displayId);
 }
 
-status_t SurfaceFlinger::getColorManagement(bool* outGetColorManagement) const {
-    if (!outGetColorManagement) {
-        return BAD_VALUE;
-    }
-    *outGetColorManagement = useColorManagement;
-    return NO_ERROR;
-}
-
 HWComposer& SurfaceFlinger::getHwComposer() const {
     return mCompositionEngine->getHwComposer();
 }
@@ -693,7 +692,7 @@
 
     // wait patiently for the window manager death
     const String16 name("window");
-    mWindowManager = defaultServiceManager()->getService(name);
+    mWindowManager = defaultServiceManager()->waitForService(name);
     if (mWindowManager != 0) {
         mWindowManager->linkToDeath(sp<IBinder::DeathRecipient>::fromExisting(this));
     }
@@ -707,7 +706,7 @@
     LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,
                    ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
 
-    sp<IBinder> input(defaultServiceManager()->getService(String16("inputflinger")));
+    sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
 
     static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (input == nullptr) {
@@ -742,54 +741,18 @@
             enableRefreshRateOverlay(true);
         }
     }));
-}
 
-uint32_t SurfaceFlinger::getNewTexture() {
-    {
-        std::lock_guard lock(mTexturePoolMutex);
-        if (!mTexturePool.empty()) {
-            uint32_t name = mTexturePool.back();
-            mTexturePool.pop_back();
-            ATRACE_INT("TexturePoolSize", mTexturePool.size());
-            return name;
-        }
+    LOG_ALWAYS_FATAL_IF(flags::misc1() != mMiscFlagValue, "misc1 flag is not boot stable!");
 
-        // The pool was too small, so increase it for the future
-        ++mTexturePoolSize;
-    }
-
-    // The pool was empty, so we need to get a new texture name directly using a
-    // blocking call to the main thread
-    auto genTextures = [this] {
-               uint32_t name = 0;
-               getRenderEngine().genTextures(1, &name);
-               return name;
-    };
-    if (std::this_thread::get_id() == mMainThreadId) {
-        return genTextures();
-    } else {
-        return mScheduler->schedule(genTextures).get();
-    }
-}
-
-void SurfaceFlinger::deleteTextureAsync(uint32_t texture) {
-    std::lock_guard lock(mTexturePoolMutex);
-    // We don't change the pool size, so the fix-up logic in postComposition will decide whether
-    // to actually delete this or not based on mTexturePoolSize
-    mTexturePool.push_back(texture);
-    ATRACE_INT("TexturePoolSize", mTexturePool.size());
+    mMisc2FlagLateBootValue = flags::late_boot_misc2();
 }
 
 static std::optional<renderengine::RenderEngine::RenderEngineType>
 chooseRenderEngineTypeViaSysProp() {
     char prop[PROPERTY_VALUE_MAX];
-    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
+    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "skiaglthreaded");
 
-    if (strcmp(prop, "gles") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::GLES;
-    } else if (strcmp(prop, "threaded") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::THREADED;
-    } else if (strcmp(prop, "skiagl") == 0) {
+    if (strcmp(prop, "skiagl") == 0) {
         return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
     } else if (strcmp(prop, "skiaglthreaded") == 0) {
         return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
@@ -806,6 +769,7 @@
 // Do not call property_set on main thread which will be blocked by init
 // Use StartPropertySetThread instead.
 void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
+    ATRACE_CALL();
     ALOGI(  "SurfaceFlinger's main thread ready to run. "
             "Initializing graphics H/W...");
     addTransactionReadyFilters();
@@ -817,7 +781,6 @@
     auto builder = renderengine::RenderEngineCreationArgs::Builder()
                            .setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat))
                            .setImageCacheSize(maxFrameBufferAcquiredBuffers)
-                           .setUseColorManagerment(useColorManagement)
                            .setEnableProtectedContext(enable_protected_contents(false))
                            .setPrecacheToneMapperShaderOnly(false)
                            .setSupportsBackgroundBlur(mSupportsBlur)
@@ -877,6 +840,17 @@
     initScheduler(display);
     dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
 
+    mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) {
+        auto snapshot = perfetto::protos::LayersSnapshotProto{};
+        mScheduler
+                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                    snapshot = takeLayersSnapshotProto(traceFlags, TimePoint::now(),
+                                                       mLastCommittedVsyncId, true);
+                })
+                .wait();
+        return snapshot;
+    });
+
     // Commit secondary display(s).
     processDisplayChangesLocked();
 
@@ -890,14 +864,14 @@
 
     mPowerAdvisor->init();
 
-    char primeShaderCache[PROPERTY_VALUE_MAX];
-    property_get("service.sf.prime_shader_cache", primeShaderCache, "1");
-    if (atoi(primeShaderCache)) {
+    if (base::GetBoolProperty("service.sf.prime_shader_cache"s, true)) {
         if (setSchedFifo(false) != NO_ERROR) {
             ALOGW("Can't set SCHED_OTHER for primeCache");
         }
 
-        mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache();
+        bool shouldPrimeUltraHDR =
+                base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+        mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
 
         if (setSchedFifo(true) != NO_ERROR) {
             ALOGW("Can't set SCHED_OTHER for primeCache");
@@ -914,30 +888,32 @@
         ALOGE("Run StartPropertySetThread failed!");
     }
 
-    if (mTransactionTracing) {
-        TransactionTraceWriter::getInstance().setWriterFunction([&](const std::string& prefix,
-                                                                    bool overwrite) {
-            auto writeFn = [&]() {
-                const std::string filename =
-                        TransactionTracing::DIR_NAME + prefix + TransactionTracing::FILE_NAME;
-                if (overwrite && access(filename.c_str(), F_OK) == 0) {
-                    ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
-                    return;
-                }
-                mTransactionTracing->flush();
-                mTransactionTracing->writeToFile(filename);
-            };
-            if (std::this_thread::get_id() == mMainThreadId) {
-                writeFn();
-            } else {
-                mScheduler->schedule(writeFn).get();
-            }
-        });
-    }
-
+    initTransactionTraceWriter();
     ALOGV("Done initializing");
 }
 
+void SurfaceFlinger::initTransactionTraceWriter() {
+    if (!mTransactionTracing) {
+        return;
+    }
+    TransactionTraceWriter::getInstance().setWriterFunction(
+            [&](const std::string& filename, bool overwrite) {
+                auto writeFn = [&]() {
+                    if (!overwrite && access(filename.c_str(), F_OK) == 0) {
+                        ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
+                        return;
+                    }
+                    mTransactionTracing->flush();
+                    mTransactionTracing->writeToFile(filename);
+                };
+                if (std::this_thread::get_id() == mMainThreadId) {
+                    writeFn();
+                } else {
+                    mScheduler->schedule(writeFn).get();
+                }
+            });
+}
+
 void SurfaceFlinger::readPersistentProperties() {
     Mutex::Autolock _l(mStateLock);
 
@@ -1069,8 +1045,9 @@
         outMode.xDpi = xDpi;
         outMode.yDpi = yDpi;
 
-        const nsecs_t period = mode->getVsyncPeriod();
-        outMode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
+        const auto peakFps = mode->getPeakFps();
+        outMode.refreshRate = peakFps.getValue();
+        outMode.vsyncRate = mode->getVsyncRate().getValue();
 
         const auto vsyncConfigSet =
                 mVsyncConfiguration->getConfigsForRefreshRate(Fps::fromValue(outMode.refreshRate));
@@ -1090,7 +1067,7 @@
         //
         // We add an additional 1ms to allow for processing time and
         // differences between the ideal and actual refresh rate.
-        outMode.presentationDeadline = period - outMode.sfVsyncOffset + 1000000;
+        outMode.presentationDeadline = peakFps.getPeriodNsecs() - outMode.sfVsyncOffset + 1000000;
         excludeDolbyVisionIf4k30Present(display->getHdrCapabilities().getSupportedHdrTypes(),
                                         outMode);
         info->supportedDisplayModes.push_back(outMode);
@@ -1225,7 +1202,7 @@
             // Start receiving vsync samples now, so that we can detect a period
             // switch.
             mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */,
-                                              mode.modePtr->getFps());
+                                              mode.modePtr->getVsyncRate());
 
             // As we called to set period, we will call to onRefreshRateChangeCompleted once
             // VsyncController model is locked.
@@ -1255,7 +1232,7 @@
 }
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
-                                                   DisplayModeId modeId) {
+                                                   DisplayModeId modeId, Fps minFps, Fps maxFps) {
     ATRACE_CALL();
 
     if (!displayToken) {
@@ -1279,7 +1256,7 @@
         const auto& snapshot = snapshotRef.get();
 
         const auto fpsOpt = snapshot.displayModes().get(modeId).transform(
-                [](const DisplayModePtr& mode) { return mode->getFps(); });
+                [](const DisplayModePtr& mode) { return mode->getPeakFps(); });
 
         if (!fpsOpt) {
             ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(),
@@ -1288,13 +1265,15 @@
         }
 
         const Fps fps = *fpsOpt;
+        const FpsRange physical = {fps, fps};
+        const FpsRange render = {minFps.isValid() ? minFps : fps, maxFps.isValid() ? maxFps : fps};
+        const FpsRanges ranges = {physical, render};
 
         // Keep the old switching type.
         const bool allowGroupSwitching =
                 display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching;
 
-        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId,
-                                                                          {fps, fps},
+        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, ranges, ranges,
                                                                           allowGroupSwitching};
 
         return setDesiredDisplayModeSpecsInternal(display, policy);
@@ -1328,7 +1307,7 @@
     }
 
     const auto& activeMode = *upcomingModeInfo.modeOpt;
-    display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getFps(),
+    display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
                                activeMode.fps);
 
     if (displayId == mActiveDisplayId) {
@@ -1353,10 +1332,10 @@
     const auto desiredActiveMode = display->getDesiredActiveMode();
     const auto& modeOpt = desiredActiveMode->modeOpt;
     const auto displayId = modeOpt->modePtr->getPhysicalDisplayId();
-    const auto displayFps = modeOpt->modePtr->getFps();
+    const auto vsyncRate = modeOpt->modePtr->getVsyncRate();
     const auto renderFps = modeOpt->fps;
     clearDesiredActiveModeState(display);
-    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps);
+    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, vsyncRate);
     mScheduler->setRenderRate(displayId, renderFps);
 
     if (displayId == mActiveDisplayId) {
@@ -1396,7 +1375,7 @@
         }
 
         ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(),
-              to_string(displayModePtrOpt->get()->getFps()).c_str(),
+              to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(display->getId()).c_str());
 
         if (display->getActiveMode() == desiredActiveMode->modeOpt) {
@@ -1523,7 +1502,7 @@
         }
 
         display->getCompositionDisplay()->setColorProfile(
-                {mode, Dataspace::UNKNOWN, RenderIntent::COLORIMETRIC, Dataspace::UNKNOWN});
+                {mode, Dataspace::UNKNOWN, RenderIntent::COLORIMETRIC});
 
         return NO_ERROR;
     });
@@ -1943,10 +1922,28 @@
                        float currentDimmingRatio =
                                compositionDisplay->editState().sdrWhitePointNits /
                                compositionDisplay->editState().displayBrightnessNits;
-                       compositionDisplay->setDisplayBrightness(brightness.sdrWhitePointNits,
-                                                                brightness.displayBrightnessNits);
+                       static constexpr float kDimmingThreshold = 0.02f;
+                       if (brightness.sdrWhitePointNits == 0.f ||
+                           abs(brightness.sdrWhitePointNits - brightness.displayBrightnessNits) /
+                                           brightness.sdrWhitePointNits >=
+                                   kDimmingThreshold) {
+                           // to optimize, skip brightness setter if the brightness difference ratio
+                           // is lower than threshold
+                           compositionDisplay
+                                   ->setDisplayBrightness(brightness.sdrWhitePointNits,
+                                                          brightness.displayBrightnessNits);
+                       } else {
+                           compositionDisplay->setDisplayBrightness(brightness.sdrWhitePointNits,
+                                                                    brightness.sdrWhitePointNits);
+                       }
+
                        FTL_FAKE_GUARD(kMainThreadContext,
                                       display->stageBrightness(brightness.displayBrightness));
+                       float currentHdrSdrRatio =
+                               compositionDisplay->editState().displayBrightnessNits /
+                               compositionDisplay->editState().sdrWhitePointNits;
+                       FTL_FAKE_GUARD(kMainThreadContext,
+                                      display->updateHdrSdrRatioOverlayRatio(currentHdrSdrRatio));
 
                        if (brightness.sdrWhitePointNits / brightness.displayBrightnessNits !=
                            currentDimmingRatio) {
@@ -2018,7 +2015,7 @@
 }
 
 status_t SurfaceFlinger::notifyPowerBoost(int32_t boostId) {
-    using hardware::power::Boost;
+    using aidl::android::hardware::power::Boost;
     Boost powerBoost = static_cast<Boost>(boostId);
 
     if (powerBoost == Boost::INTERACTION) {
@@ -2090,6 +2087,17 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
+    if (mConnectedDisplayFlagValue) {
+        // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
+        if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
+            vsyncPeriod.value() == ~0) {
+            int hotplugErrorCode = static_cast<int32_t>(-timestamp);
+            ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
+            mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
+            return;
+        }
+    }
+
     ATRACE_NAME(vsyncPeriod
                         ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
                         : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
@@ -2167,12 +2175,22 @@
     }
 }
 
-bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update,
-                                                bool transactionsFlushed,
+bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs,
+                                                bool flushTransactions,
                                                 bool& outTransactionsAreEmpty) {
     ATRACE_CALL();
+    frontend::Update update;
+    if (flushTransactions) {
+        update = flushLifecycleUpdates();
+        if (mTransactionTracing) {
+            mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId), frameTimeNs,
+                                                          update, mFrontEndDisplayInfos,
+                                                          mFrontEndDisplayInfosChanged);
+        }
+    }
+
     bool needsTraversal = false;
-    if (transactionsFlushed) {
+    if (flushTransactions) {
         needsTraversal |= commitMirrorDisplays(vsyncId);
         needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates);
         needsTraversal |= applyTransactions(update.transactions, vsyncId);
@@ -2188,44 +2206,104 @@
     return mustComposite;
 }
 
-void SurfaceFlinger::updateLayerHistory(const frontend::LayerSnapshot& snapshot) {
-    using Changes = frontend::RequestedLayerState::Changes;
-    if (snapshot.path.isClone() ||
-        !snapshot.changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation)) {
-        return;
-    }
+void SurfaceFlinger::updateLayerHistory(nsecs_t now) {
+    for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+        using Changes = frontend::RequestedLayerState::Changes;
+        if (snapshot->path.isClone()) {
+            continue;
+        }
 
-    const auto layerProps = scheduler::LayerProps{
-            .visible = snapshot.isVisible,
-            .bounds = snapshot.geomLayerBounds,
-            .transform = snapshot.geomLayerTransform,
-            .setFrameRateVote = snapshot.frameRate,
-            .frameRateSelectionPriority = snapshot.frameRateSelectionPriority,
-    };
+        const bool updateSmallDirty = mScheduler->supportSmallDirtyDetection() &&
+                ((snapshot->clientChanges & layer_state_t::eSurfaceDamageRegionChanged) ||
+                 snapshot->changes.any(Changes::Geometry));
 
-    auto it = mLegacyLayers.find(snapshot.sequence);
-    LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
-                        snapshot.getDebugString().c_str());
+        const bool hasChanges =
+                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) ||
+                (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) !=
+                        0;
 
-    if (snapshot.changes.test(Changes::Animation)) {
-        it->second->recordLayerHistoryAnimationTx(layerProps);
-    }
+        if (!updateSmallDirty && !hasChanges) {
+            continue;
+        }
 
-    if (snapshot.changes.test(Changes::FrameRate)) {
-        it->second->setFrameRateForLayerTree(snapshot.frameRate, layerProps);
-    }
+        auto it = mLegacyLayers.find(snapshot->sequence);
+        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldn't find layer object for %s",
+                            snapshot->getDebugString().c_str());
 
-    if (snapshot.changes.test(Changes::Buffer)) {
-        it->second->recordLayerHistoryBufferUpdate(layerProps);
+        if (updateSmallDirty) {
+            // Update small dirty flag while surface damage region or geometry changed
+            it->second->setIsSmallDirty(snapshot.get());
+        }
+
+        if (!hasChanges) {
+            continue;
+        }
+
+        const auto layerProps = scheduler::LayerProps{
+                .visible = snapshot->isVisible,
+                .bounds = snapshot->geomLayerBounds,
+                .transform = snapshot->geomLayerTransform,
+                .setFrameRateVote = snapshot->frameRate,
+                .frameRateSelectionPriority = snapshot->frameRateSelectionPriority,
+                .isSmallDirty = snapshot->isSmallDirty,
+        };
+
+        if (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
+            mScheduler->setDefaultFrameRateCompatibility(snapshot->sequence,
+                                                         snapshot->defaultFrameRateCompatibility);
+        }
+
+        if (snapshot->changes.test(Changes::Animation)) {
+            it->second->recordLayerHistoryAnimationTx(layerProps, now);
+        }
+
+        if (snapshot->changes.test(Changes::FrameRate)) {
+            it->second->setFrameRateForLayerTree(snapshot->frameRate, layerProps, now);
+        }
+
+        if (snapshot->changes.test(Changes::Buffer)) {
+            it->second->recordLayerHistoryBufferUpdate(layerProps, now);
+        }
     }
 }
 
-bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update,
-                                          bool transactionsFlushed, bool& outTransactionsAreEmpty) {
+bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs,
+                                          bool flushTransactions, bool& outTransactionsAreEmpty) {
     using Changes = frontend::RequestedLayerState::Changes;
     ATRACE_CALL();
-    {
+    frontend::Update update;
+    if (flushTransactions) {
+        ATRACE_NAME("TransactionHandler:flushTransactions");
+        // Locking:
+        // 1. to prevent onHandleDestroyed from being called while the state lock is held,
+        // we must keep a copy of the transactions (specifically the composer
+        // states) around outside the scope of the lock.
+        // 2. Transactions and created layers do not share a lock. To prevent applying
+        // transactions with layers still in the createdLayer queue, collect the transactions
+        // before committing the created layers.
+        // 3. Transactions can only be flushed after adding layers, since the layer can be a newly
+        // created one
+        mTransactionHandler.collectTransactions();
+        {
+            // TODO(b/238781169) lockless queue this and keep order.
+            std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+            update.layerCreatedStates = std::move(mCreatedLayers);
+            mCreatedLayers.clear();
+            update.newLayers = std::move(mNewLayers);
+            mNewLayers.clear();
+            update.layerCreationArgs = std::move(mNewLayerArgs);
+            mNewLayerArgs.clear();
+            update.destroyedHandles = std::move(mDestroyedHandles);
+            mDestroyedHandles.clear();
+        }
+
         mLayerLifecycleManager.addLayers(std::move(update.newLayers));
+        update.transactions = mTransactionHandler.flushTransactions();
+        if (mTransactionTracing) {
+            mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId), frameTimeNs,
+                                                          update, mFrontEndDisplayInfos,
+                                                          mFrontEndDisplayInfosChanged);
+        }
         mLayerLifecycleManager.applyTransactions(update.transactions);
         mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles);
         for (auto& legacyLayer : update.layerCreatedStates) {
@@ -2234,11 +2312,11 @@
                 mLegacyLayers[layer->sequence] = layer;
             }
         }
-    }
-    if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
-        ATRACE_NAME("LayerHierarchyBuilder:update");
-        mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
-                                      mLayerLifecycleManager.getDestroyedLayers());
+        if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
+            ATRACE_NAME("LayerHierarchyBuilder:update");
+            mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
+                                          mLayerLifecycleManager.getDestroyedLayers());
+        }
     }
 
     bool mustComposite = false;
@@ -2256,7 +2334,9 @@
                      .forceFullDamage = mForceFullDamage,
                      .supportedLayerGenericMetadata =
                              getHwComposer().getSupportedLayerGenericMetadata(),
-                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()};
+                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap(),
+                     .skipRoundCornersWhenProtected =
+                             !getRenderEngine().supportsProtectedContent()};
         mLayerSnapshotBuilder.update(args);
     }
 
@@ -2265,16 +2345,22 @@
         mUpdateInputInfo = true;
     }
     if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy |
-                                                      Changes::Visibility)) {
+                                                      Changes::Visibility | Changes::Geometry)) {
         mVisibleRegionsDirty = true;
     }
+    if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Hierarchy | Changes::FrameRate)) {
+        // The frame rate of attached choreographers can only change as a result of a
+        // FrameRate change (including when Hierarchy changes).
+        mUpdateAttachedChoreographer = true;
+    }
     outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0;
     mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
 
     bool newDataLatched = false;
     if (!mLegacyFrontEndEnabled) {
         ATRACE_NAME("DisplayCallbackAndStatsUpdates");
-        applyTransactions(update.transactions, vsyncId);
+        mustComposite |= applyTransactions(update.transactions, vsyncId);
+        traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
         const nsecs_t latchTime = systemTime();
         bool unused = false;
 
@@ -2288,31 +2374,44 @@
                 mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
             }
             const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
-            if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) continue;
 
             auto it = mLegacyLayers.find(layer->id);
             LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
                                 layer->getDebugString().c_str());
+            if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
+                if (!it->second->hasBuffer()) {
+                    // The last latch time is used to classify a missed frame as buffer stuffing
+                    // instead of a missed frame. This is used to identify scenarios where we
+                    // could not latch a buffer or apply a transaction due to backpressure.
+                    // We only update the latch time for buffer less layers here, the latch time
+                    // is updated for buffer layers when the buffer is latched.
+                    it->second->updateLastLatchTime(latchTime);
+                }
+                continue;
+            }
+
             const bool bgColorOnly =
                     !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
             if (willReleaseBufferOnLatch) {
                 mLayersWithBuffersRemoved.emplace(it->second);
             }
             it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
-            mLayersWithQueuedFrames.emplace(it->second);
-        }
-
-        for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
-            updateLayerHistory(*snapshot);
-            if (!snapshot->hasReadyFrame) continue;
             newDataLatched = true;
-            if (!snapshot->isVisible) break;
 
-            Region visibleReg;
-            visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion);
-            invalidateLayerStack(snapshot->outputFilter, visibleReg);
+            mLayersWithQueuedFrames.emplace(it->second);
+            mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
         }
 
+        updateLayerHistory(latchTime);
+        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+            if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) ==
+                mLayersIdsWithQueuedFrames.end())
+                return;
+            Region visibleReg;
+            visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
+            invalidateLayerStack(snapshot.outputFilter, visibleReg);
+        });
+
         for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
             mLegacyLayers.erase(destroyedLayer->id);
         }
@@ -2322,8 +2421,6 @@
             mLayerLifecycleManager.commitChanges();
         }
 
-        commitTransactions();
-
         // enter boot animation on first buffer latch
         if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
             ALOGI("Enter boot animation");
@@ -2331,6 +2428,10 @@
         }
     }
     mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched;
+    if (mustComposite && !mLegacyFrontEndEnabled) {
+        commitTransactions();
+    }
+
     return mustComposite;
 }
 
@@ -2345,11 +2446,6 @@
         mTimeStats->incrementMissedFrames();
     }
 
-    if (mTracingEnabledChanged) {
-        mLayerTracingEnabled = mLayerTracing.isEnabled();
-        mTracingEnabledChanged = false;
-    }
-
     // If a mode set is pending and the fence hasn't fired yet, wait for the next commit.
     if (std::any_of(frameTargets.begin(), frameTargets.end(),
                     [this](const auto& pair) FTL_FAKE_GUARD(mStateLock)
@@ -2414,10 +2510,10 @@
         mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod);
     }
 
-    if (mRefreshRateOverlaySpinner) {
+    if (mRefreshRateOverlaySpinner || mHdrSdrRatioOverlay) {
         Mutex::Autolock lock(mStateLock);
         if (const auto display = getDefaultDisplayDeviceLocked()) {
-            display->animateRefreshRateOverlay();
+            display->animateOverlay();
         }
     }
 
@@ -2426,28 +2522,20 @@
     {
         mFrameTimeline->setSfWakeUp(ftl::to_underlying(vsyncId),
                                     pacesetterFrameTarget.frameBeginTime().ns(),
-                                    Fps::fromPeriodNsecs(vsyncPeriod.ns()));
+                                    Fps::fromPeriodNsecs(vsyncPeriod.ns()),
+                                    mScheduler->getPacesetterRefreshRate());
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
-        frontend::Update updates;
-        if (flushTransactions) {
-            updates = flushLifecycleUpdates();
-            if (mTransactionTracing) {
-                mTransactionTracing
-                        ->addCommittedTransactions(ftl::to_underlying(vsyncId),
-                                                   pacesetterFrameTarget.frameBeginTime().ns(),
-                                                   updates, mFrontEndDisplayInfos,
-                                                   mFrontEndDisplayInfosChanged);
-            }
-        }
         bool transactionsAreEmpty;
         if (mLegacyFrontEndEnabled) {
-            mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions,
-                                                        transactionsAreEmpty);
+            mustComposite |=
+                    updateLayerSnapshotsLegacy(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
+                                               flushTransactions, transactionsAreEmpty);
         }
         if (mLayerLifecycleManagerEnabled) {
             mustComposite |=
-                    updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty);
+                    updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
+                                         flushTransactions, transactionsAreEmpty);
         }
 
         if (transactionFlushNeeded()) {
@@ -2470,18 +2558,24 @@
     // Hold mStateLock as chooseRefreshRateForContent promotes wp<Layer> to sp<Layer>
     // and may eventually call to ~Layer() if it holds the last reference
     {
+        bool updateAttachedChoreographer = mUpdateAttachedChoreographer;
+        mUpdateAttachedChoreographer = false;
+
         Mutex::Autolock lock(mStateLock);
-        mScheduler->chooseRefreshRateForContent();
+        mScheduler->chooseRefreshRateForContent(mLayerLifecycleManagerEnabled
+                                                        ? &mLayerHierarchyBuilder.getHierarchy()
+                                                        : nullptr,
+                                                updateAttachedChoreographer);
         initiateDisplayModeChanges();
     }
 
     updateCursorAsync();
-    updateInputFlinger(vsyncId, pacesetterFrameTarget.frameBeginTime());
-
-    if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
-        // This will block and tracing should only be enabled for debugging.
-        addToLayerTracing(mVisibleRegionsDirty, pacesetterFrameTarget.frameBeginTime(), vsyncId);
+    if (!mustComposite) {
+        updateInputFlinger(vsyncId, pacesetterFrameTarget.frameBeginTime());
     }
+    doActiveLayersTracingIfNeeded(false, mVisibleRegionsDirty,
+                                  pacesetterFrameTarget.frameBeginTime(), vsyncId);
+
     mLastCommittedVsyncId = vsyncId;
 
     persistDisplayBrightness(mustComposite);
@@ -2558,10 +2652,7 @@
             refreshArgs.layersWithQueuedFrames.push_back(layerFE);
     }
 
-    refreshArgs.outputColorSetting = useColorManagement
-            ? mDisplayColorSetting
-            : compositionengine::OutputColorSetting::kUnmanaged;
-    refreshArgs.colorSpaceAgnosticDataspace = mColorSpaceAgnosticDataspace;
+    refreshArgs.outputColorSetting = mDisplayColorSetting;
     refreshArgs.forceOutputColorMode = mForceColorMode;
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
@@ -2603,6 +2694,28 @@
     constexpr bool kCursorOnly = false;
     const auto layers = moveSnapshotsToCompositionArgs(refreshArgs, kCursorOnly);
 
+    if (mLayerLifecycleManagerEnabled && !mVisibleRegionsDirty) {
+        for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
+            auto compositionDisplay = display->getCompositionDisplay();
+            if (!compositionDisplay->getState().isEnabled) continue;
+            for (auto outputLayer : compositionDisplay->getOutputLayersOrderedByZ()) {
+                if (outputLayer->getLayerFE().getCompositionState() == nullptr) {
+                    // This is unexpected but instead of crashing, capture traces to disk
+                    // and recover gracefully by forcing CE to rebuild layer stack.
+                    ALOGE("Output layer %s for display %s %" PRIu64 " has a null "
+                          "snapshot. Forcing mVisibleRegionsDirty",
+                          outputLayer->getLayerFE().getDebugName(),
+                          compositionDisplay->getName().c_str(), compositionDisplay->getId().value);
+
+                    TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false);
+                    mVisibleRegionsDirty = true;
+                    refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
+                    refreshArgs.updatingGeometryThisFrame = mVisibleRegionsDirty;
+                }
+            }
+        }
+    }
+
     mCompositionEngine->present(refreshArgs);
     moveSnapshotsFromCompositionArgs(refreshArgs, layers);
 
@@ -2683,10 +2796,11 @@
     mScheduler->modulateVsync({}, &VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse);
 
     mLayersWithQueuedFrames.clear();
-    if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
-        // This will block and should only be used for debugging.
-        addToLayerTracing(mVisibleRegionsDirty, pacesetterTarget.frameBeginTime(), vsyncId);
-    }
+    mLayersIdsWithQueuedFrames.clear();
+    doActiveLayersTracingIfNeeded(true, mVisibleRegionsDirty, pacesetterTarget.frameBeginTime(),
+                                  vsyncId);
+
+    updateInputFlinger(vsyncId, pacesetterTarget.frameBeginTime());
 
     if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true;
     mVisibleRegionsDirty = false;
@@ -2922,31 +3036,55 @@
         for (auto& [compositionDisplay, listener] : hdrInfoListeners) {
             HdrLayerInfoReporter::HdrLayerInfo info;
             int32_t maxArea = 0;
-            mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
-                const auto layerFe = layer->getCompositionEngineLayerFE();
-                const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot();
-                if (snapshot.isVisible &&
-                    compositionDisplay->includesLayer(snapshot.outputFilter)) {
-                    if (isHdrLayer(snapshot)) {
-                        const auto* outputLayer =
-                            compositionDisplay->getOutputLayerForLayer(layerFe);
-                        if (outputLayer) {
-                            const float desiredHdrSdrRatio = snapshot.desiredHdrSdrRatio <= 1.f
-                                    ? std::numeric_limits<float>::infinity()
-                                    : snapshot.desiredHdrSdrRatio;
-                            info.mergeDesiredRatio(desiredHdrSdrRatio);
-                            info.numberOfHdrLayers++;
-                            const auto displayFrame = outputLayer->getState().displayFrame;
-                            const int32_t area = displayFrame.width() * displayFrame.height();
-                            if (area > maxArea) {
-                                maxArea = area;
-                                info.maxW = displayFrame.width();
-                                info.maxH = displayFrame.height();
+            auto updateInfoFn =
+                    [&](const std::shared_ptr<compositionengine::Display>& compositionDisplay,
+                        const frontend::LayerSnapshot& snapshot, const sp<LayerFE>& layerFe) {
+                        if (snapshot.isVisible &&
+                            compositionDisplay->includesLayer(snapshot.outputFilter)) {
+                            if (isHdrLayer(snapshot)) {
+                                const auto* outputLayer =
+                                        compositionDisplay->getOutputLayerForLayer(layerFe);
+                                if (outputLayer) {
+                                    const float desiredHdrSdrRatio =
+                                            snapshot.desiredHdrSdrRatio <= 1.f
+                                            ? std::numeric_limits<float>::infinity()
+                                            : snapshot.desiredHdrSdrRatio;
+                                    info.mergeDesiredRatio(desiredHdrSdrRatio);
+                                    info.numberOfHdrLayers++;
+                                    const auto displayFrame = outputLayer->getState().displayFrame;
+                                    const int32_t area =
+                                            displayFrame.width() * displayFrame.height();
+                                    if (area > maxArea) {
+                                        maxArea = area;
+                                        info.maxW = displayFrame.width();
+                                        info.maxH = displayFrame.height();
+                                    }
+                                }
                             }
                         }
-                    }
-                }
-            });
+                    };
+
+            if (mLayerLifecycleManagerEnabled) {
+                mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                        [&, compositionDisplay = compositionDisplay](
+                                std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                            auto it = mLegacyLayers.find(snapshot->sequence);
+                            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(),
+                                                "Couldnt find layer object for %s",
+                                                snapshot->getDebugString().c_str());
+                            auto& legacyLayer = it->second;
+                            sp<LayerFE> layerFe =
+                                    legacyLayer->getCompositionEngineLayerFE(snapshot->path);
+
+                            updateInfoFn(compositionDisplay, *snapshot, layerFe);
+                        });
+            } else {
+                mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
+                    const auto layerFe = layer->getCompositionEngineLayerFE();
+                    const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot();
+                    updateInfoFn(compositionDisplay, snapshot, layerFe);
+                });
+            }
             listener->dispatchHdrLayerInfo(info);
         }
     }
@@ -2978,10 +3116,6 @@
         }
     }
 
-    const size_t sfConnections = mScheduler->getEventThreadConnectionCount(mSfConnectionHandle);
-    const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle);
-    mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
-
     if (hasPacesetterDisplay && !pacesetterDisplay->isPoweredOn()) {
         getRenderEngine().cleanupPostRender();
         return;
@@ -2990,23 +3124,6 @@
     // Cleanup any outstanding resources due to rendering a prior frame.
     getRenderEngine().cleanupPostRender();
 
-    {
-        std::lock_guard lock(mTexturePoolMutex);
-        if (mTexturePool.size() < mTexturePoolSize) {
-            const size_t refillCount = mTexturePoolSize - mTexturePool.size();
-            const size_t offset = mTexturePool.size();
-            mTexturePool.resize(mTexturePoolSize);
-            getRenderEngine().genTextures(refillCount, mTexturePool.data() + offset);
-            ATRACE_INT("TexturePoolSize", mTexturePool.size());
-        } else if (mTexturePool.size() > mTexturePoolSize) {
-            const size_t deleteCount = mTexturePool.size() - mTexturePoolSize;
-            const size_t offset = mTexturePoolSize;
-            getRenderEngine().deleteTextures(deleteCount, mTexturePool.data() + offset);
-            mTexturePool.resize(mTexturePoolSize);
-            ATRACE_INT("TexturePoolSize", mTexturePool.size());
-        }
-    }
-
     if (mNumTrustedPresentationListeners > 0) {
         // We avoid any reverse traversal upwards so this shouldn't be too expensive
         traverseLegacyLayers([&](Layer* layer) {
@@ -3097,7 +3214,9 @@
     int attempt = 0;
     constexpr int kMaxAttempts = 3;
     do {
-        hwcModes = getHwComposer().getModes(displayId);
+        hwcModes = getHwComposer().getModes(displayId,
+                                            scheduler::RefreshRateSelector::kMinSupportedFrameRate
+                                                    .getPeriodNsecs());
         activeModeHwcId = getHwComposer().getActiveMode(displayId);
 
         const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
@@ -3139,6 +3258,7 @@
                                      .setPhysicalDisplayId(displayId)
                                      .setResolution({hwcMode.width, hwcMode.height})
                                      .setVsyncPeriod(hwcMode.vsyncPeriod)
+                                     .setVrrConfig(hwcMode.vrrConfig)
                                      .setDpiX(hwcMode.dpiX)
                                      .setDpiY(hwcMode.dpiY)
                                      .setGroup(hwcMode.configGroup)
@@ -3308,18 +3428,16 @@
 
         creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked();
 
-        if (useColorManagement) {
-            mPhysicalDisplays.get(physical->id)
-                    .transform(&PhysicalDisplay::snapshotRef)
-                    .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) {
-                        for (const auto mode : snapshot.colorModes()) {
-                            creationArgs.hasWideColorGamut |= ui::isWideColorMode(mode);
-                            creationArgs.hwcColorModes
-                                    .emplace(mode,
-                                             getHwComposer().getRenderIntents(physical->id, mode));
-                        }
-                    }));
-        }
+        mPhysicalDisplays.get(physical->id)
+                .transform(&PhysicalDisplay::snapshotRef)
+                .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) {
+                    for (const auto mode : snapshot.colorModes()) {
+                        creationArgs.hasWideColorGamut |= ui::isWideColorMode(mode);
+                        creationArgs.hwcColorModes
+                                .emplace(mode,
+                                         getHwComposer().getRenderIntents(physical->id, mode));
+                    }
+                }));
     }
 
     if (const auto id = HalDisplayId::tryCast(compositionDisplay->getId())) {
@@ -3360,12 +3478,11 @@
     }
     display->getCompositionDisplay()->setColorProfile(
             compositionengine::Output::ColorProfile{defaultColorMode, defaultDataSpace,
-                                                    RenderIntent::COLORIMETRIC,
-                                                    Dataspace::UNKNOWN});
+                                                    RenderIntent::COLORIMETRIC});
 
     if (const auto& physical = state.physical) {
         const auto& mode = *physical->activeMode;
-        display->setActiveMode(mode.getId(), mode.getFps(), mode.getFps());
+        display->setActiveMode(mode.getId(), mode.getVsyncRate(), mode.getVsyncRate());
     }
 
     display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack));
@@ -3505,8 +3622,6 @@
 
     // Recreate the DisplayDevice if the surface or sequence ID changed.
     if (currentBinder != drawingBinder || currentState.sequenceId != drawingState.sequenceId) {
-        getRenderEngine().cleanFramebufferCache();
-
         if (const auto display = getDisplayDeviceLocked(displayToken)) {
             display->disconnect();
             if (display->isVirtual()) {
@@ -3912,6 +4027,14 @@
     mPowerAdvisor->notifyCpuLoadUp();
 }
 
+void SurfaceFlinger::onChoreographerAttached() {
+    ATRACE_CALL();
+    if (mLayerLifecycleManagerEnabled) {
+        mUpdateAttachedChoreographer = true;
+        scheduleCommit(FrameHint::kNone);
+    }
+}
+
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
     using namespace scheduler;
 
@@ -3928,7 +4051,7 @@
 
     if (sysprop::use_content_detection_for_refresh_rate(false)) {
         features |= Feature::kContentDetection;
-        if (base::GetBoolProperty("debug.sf.enable_small_dirty_detection"s, false)) {
+        if (flags::enable_small_area_detection()) {
             features |= Feature::kSmallDirtyContentDetection;
         }
     }
@@ -3974,6 +4097,9 @@
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
+
+    mIsHotplugErrViaNegVsync =
+            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
 }
 
 void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
@@ -4297,18 +4423,16 @@
     return TransactionReadiness::Ready;
 }
 
-TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck(
+TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheckLegacy(
         const TransactionHandler::TransactionFlushState& flushState) {
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     auto ready = TransactionReadiness::Ready;
-    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s,
-                                                                   const std::shared_ptr<
-                                                                           renderengine::
-                                                                                   ExternalTexture>&
-                                                                           externalTexture)
-                                                                       -> bool {
-        sp<Layer> layer = LayerHandle::getLayer(s.surface);
+    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
+                                                                           resolvedState) -> bool {
+        sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
+
         const auto& transaction = *flushState.transaction;
+        const auto& s = resolvedState.state;
         // check for barrier frames
         if (s.bufferData->hasBarrier) {
             // The current producerId is already a newer producer than the buffer that has a
@@ -4316,7 +4440,7 @@
             // don't wait on the barrier since we know that's stale information.
             if (layer->getDrawingState().barrierProducerId > s.bufferData->producerId) {
                 layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener,
-                                                 externalTexture->getBuffer(),
+                                                 resolvedState.externalTexture->getBuffer(),
                                                  s.bufferData->frameNumber,
                                                  s.bufferData->acquireFence);
                 // Delete the entire state at this point and not just release the buffer because
@@ -4353,18 +4477,17 @@
             return TraverseBuffersReturnValues::STOP_TRAVERSAL;
         }
 
-        // ignore the acquire fence if LatchUnsignaledConfig::Always is set.
-        const bool checkAcquireFence = enableLatchUnsignaledConfig != LatchUnsignaledConfig::Always;
         const bool acquireFenceAvailable = s.bufferData &&
                 s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
                 s.bufferData->acquireFence;
-        const bool fenceSignaled = !checkAcquireFence || !acquireFenceAvailable ||
+        const bool fenceSignaled = !acquireFenceAvailable ||
                 s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
         if (!fenceSignaled) {
             // check fence status
-            const bool allowLatchUnsignaled =
-                    shouldLatchUnsignaled(layer, s, transaction.states.size(),
-                                          flushState.firstTransaction);
+            const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
+                                                                    flushState.firstTransaction) &&
+                    layer->isSimpleBufferUpdate(s);
+
             if (allowLatchUnsignaled) {
                 ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
                               layer->getDebugName());
@@ -4393,15 +4516,121 @@
     return ready;
 }
 
+TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck(
+        const TransactionHandler::TransactionFlushState& flushState) {
+    using TransactionReadiness = TransactionHandler::TransactionReadiness;
+    auto ready = TransactionReadiness::Ready;
+    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
+                                                                           resolvedState) -> bool {
+        const frontend::RequestedLayerState* layer =
+                mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
+        const auto& transaction = *flushState.transaction;
+        const auto& s = resolvedState.state;
+        // check for barrier frames
+        if (s.bufferData->hasBarrier) {
+            // The current producerId is already a newer producer than the buffer that has a
+            // barrier. This means the incoming buffer is older and we can release it here. We
+            // don't wait on the barrier since we know that's stale information.
+            if (layer->barrierProducerId > s.bufferData->producerId) {
+                if (s.bufferData->releaseBufferListener) {
+                    uint32_t currentMaxAcquiredBufferCount =
+                            getMaxAcquiredBufferCountForCurrentRefreshRate(layer->ownerUid.val());
+                    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+                                          layer->name.c_str(), s.bufferData->frameNumber);
+                    s.bufferData->releaseBufferListener
+                            ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()->getId(),
+                                               s.bufferData->frameNumber},
+                                              s.bufferData->acquireFence
+                                                      ? s.bufferData->acquireFence
+                                                      : Fence::NO_FENCE,
+                                              currentMaxAcquiredBufferCount);
+                }
+
+                // Delete the entire state at this point and not just release the buffer because
+                // everything associated with the Layer in this Transaction is now out of date.
+                ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", layer->name.c_str(),
+                              layer->barrierProducerId, s.bufferData->producerId);
+                return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+            }
+
+            if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) {
+                const bool willApplyBarrierFrame =
+                        flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
+                        ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
+                          s.bufferData->barrierFrameNumber));
+                if (!willApplyBarrierFrame) {
+                    ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
+                                  layer->name.c_str(), layer->barrierFrameNumber,
+                                  s.bufferData->barrierFrameNumber);
+                    ready = TransactionReadiness::NotReadyBarrier;
+                    return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                }
+            }
+        }
+
+        // If backpressure is enabled and we already have a buffer to commit, keep
+        // the transaction in the queue.
+        const bool hasPendingBuffer =
+                flushState.bufferLayersReadyToPresent.contains(s.surface.get());
+        if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
+            ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
+            ready = TransactionReadiness::NotReady;
+            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+        }
+
+        const bool acquireFenceAvailable = s.bufferData &&
+                s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+                s.bufferData->acquireFence;
+        const bool fenceSignaled = !acquireFenceAvailable ||
+                s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
+        if (!fenceSignaled) {
+            // check fence status
+            const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
+                                                                    flushState.firstTransaction) &&
+                    layer->isSimpleBufferUpdate(s);
+            if (allowLatchUnsignaled) {
+                ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", layer->name.c_str());
+                ready = TransactionReadiness::NotReadyUnsignaled;
+            } else {
+                ready = TransactionReadiness::NotReady;
+                auto& listener = s.bufferData->releaseBufferListener;
+                if (listener &&
+                    (flushState.queueProcessTime - transaction.postTime) >
+                            std::chrono::nanoseconds(4s).count()) {
+                    mTransactionHandler
+                            .onTransactionQueueStalled(transaction.id,
+                                                       {.pid = layer->ownerPid.val(),
+                                                        .layerId = layer->id,
+                                                        .layerName = layer->name,
+                                                        .bufferId = s.bufferData->getId(),
+                                                        .frameNumber = s.bufferData->frameNumber});
+                }
+                ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+                return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+            }
+        }
+        return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
+    });
+    return ready;
+}
+
 void SurfaceFlinger::addTransactionReadyFilters() {
     mTransactionHandler.addTransactionReadyFilter(
             std::bind(&SurfaceFlinger::transactionReadyTimelineCheck, this, std::placeholders::_1));
-    mTransactionHandler.addTransactionReadyFilter(
-            std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this, std::placeholders::_1));
+    if (mLayerLifecycleManagerEnabled) {
+        mTransactionHandler.addTransactionReadyFilter(
+                std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this,
+                          std::placeholders::_1));
+    } else {
+        mTransactionHandler.addTransactionReadyFilter(
+                std::bind(&SurfaceFlinger::transactionReadyBufferCheckLegacy, this,
+                          std::placeholders::_1));
+    }
 }
 
 // For tests only
 bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) {
+    mTransactionHandler.collectTransactions();
     std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions();
     return applyTransactions(transactions, vsyncId);
 }
@@ -4454,18 +4683,13 @@
             predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold;
 }
 
-bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t& state,
-                                           size_t numStates, bool firstTransaction) const {
+bool SurfaceFlinger::shouldLatchUnsignaled(const layer_state_t& state, size_t numStates,
+                                           bool firstTransaction) const {
     if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) {
         ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
         return false;
     }
 
-    if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Always) {
-        ATRACE_FORMAT_INSTANT("%s: true (LatchUnsignaledConfig::Always)", __func__);
-        return true;
-    }
-
     // We only want to latch unsignaled when a single layer is updated in this
     // transaction (i.e. not a blast sync transaction).
     if (numStates != 1) {
@@ -4492,7 +4716,7 @@
         }
     }
 
-    return layer->isSimpleBufferUpdate(state);
+    return true;
 }
 
 status_t SurfaceFlinger::setTransactionState(
@@ -4634,7 +4858,7 @@
     for (const auto& listener : listenerCallbacks) {
         mTransactionCallbackInvoker.addEmptyTransaction(listener);
     }
-
+    nsecs_t now = systemTime();
     uint32_t clientStateFlags = 0;
     for (auto& resolvedState : states) {
         if (mLegacyFrontEndEnabled) {
@@ -4656,7 +4880,7 @@
                         .setFrameRateVote = layer->getFrameRateForLayerTree(),
                         .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
                 };
-                layer->recordLayerHistoryAnimationTx(layerProps);
+                layer->recordLayerHistoryAnimationTx(layerProps, now);
             }
         }
     }
@@ -4970,7 +5194,8 @@
         if (layer->setApi(s.api)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eSidebandStreamChanged) {
-        if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+            flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eInputInfoChanged) {
         layer->setInputInfo(*s.windowInfoHandle->getInfo());
@@ -5020,9 +5245,23 @@
         const auto strategy =
             Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
 
-        if (layer->setFrameRate(
-                Layer::FrameRate(Fps::fromValue(s.frameRate), compatibility, strategy))) {
-          flags |= eTraversalNeeded;
+        if (layer->setFrameRate(Layer::FrameRate::FrameRateVote(Fps::fromValue(s.frameRate),
+                                                                compatibility, strategy))) {
+            flags |= eTraversalNeeded;
+        }
+    }
+    if (what & layer_state_t::eFrameRateCategoryChanged) {
+        const FrameRateCategory category = Layer::FrameRate::convertCategory(s.frameRateCategory);
+        if (layer->setFrameRateCategory(category, s.frameRateCategorySmoothSwitchOnly)) {
+            flags |= eTraversalNeeded;
+        }
+    }
+    if (what & layer_state_t::eFrameRateSelectionStrategyChanged) {
+        const scheduler::LayerInfo::FrameRateSelectionStrategy strategy =
+                scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                        s.frameRateSelectionStrategy);
+        if (layer->setFrameRateSelectionStrategy(strategy)) {
+            flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eFixedTransformHintChanged) {
@@ -5204,11 +5443,27 @@
     // TODO(b/238781169) remove after screenshot refactor, currently screenshots
     // requires to read drawing state from binder thread. So we need to fix that
     // before removing this.
+    if (what & layer_state_t::eBufferTransformChanged) {
+        if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded;
+    }
+    if (what & layer_state_t::eTransformToDisplayInverseChanged) {
+        if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse))
+            flags |= eTraversalNeeded;
+    }
     if (what & layer_state_t::eCropChanged) {
         if (layer->setCrop(s.crop)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eSidebandStreamChanged) {
-        if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+            flags |= eTraversalNeeded;
+    }
+    if (what & layer_state_t::eDataspaceChanged) {
+        if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
+    }
+    if (what & layer_state_t::eExtendedRangeBrightnessChanged) {
+        if (layer->setExtendedRangeBrightness(s.currentHdrSdrRatio, s.desiredHdrSdrRatio)) {
+            flags |= eTraversalNeeded;
+        }
     }
     if (what & layer_state_t::eBufferChanged) {
         std::optional<ui::Transform::RotationFlags> transformHint = std::nullopt;
@@ -5384,7 +5639,6 @@
 
 status_t SurfaceFlinger::createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* handle,
                                                 sp<Layer>* outLayer) {
-    args.textureName = getNewTexture();
     *outLayer = getFactory().createBufferStateLayer(args);
     *handle = (*outLayer)->getHandle();
     return NO_ERROR;
@@ -5406,7 +5660,7 @@
 void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
     {
         std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-        mDestroyedHandles.emplace_back(layerId);
+        mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
     }
 
     mTransactionHandler.onLayerDestroyed(layerId);
@@ -5491,7 +5745,7 @@
 
     display->setPowerMode(mode);
 
-    const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps();
+    const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getVsyncRate();
     if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
         // Turn on the display
 
@@ -5623,6 +5877,20 @@
         StringAppendF(&result, "Permission Denial: can't dump SurfaceFlinger from pid=%d, uid=%d\n",
                       pid, uid);
     } else {
+        Dumper hwclayersDump = [this](const DumpArgs&, bool, std::string& result)
+                                       FTL_FAKE_GUARD(mStateLock) -> void const {
+            if (mLayerLifecycleManagerEnabled) {
+                mScheduler
+                        ->schedule([this, &result]() FTL_FAKE_GUARD(kMainThreadContext)
+                                           FTL_FAKE_GUARD(mStateLock) {
+                                               dumpHwcLayersMinidump(result);
+                                           })
+                        .get();
+            } else {
+                dumpHwcLayersMinidumpLockedLegacy(result);
+            }
+        };
+
         static const std::unordered_map<std::string, Dumper> dumpers = {
                 {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
                 {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
@@ -5630,7 +5898,8 @@
                 {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
                 {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
                 {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
-                {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)},
+                {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
+                {"--hwclayers"s, std::move(hwclayersDump)},
                 {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
                 {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
                 {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
@@ -5639,6 +5908,7 @@
                 {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
                 {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
                 {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
+                {"--frontend"s, dumper(&SurfaceFlinger::dumpFrontEnd)},
         };
 
         const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
@@ -5648,17 +5918,56 @@
         // traversals, which can result in use-after-frees.
         std::string compositionLayers;
         mScheduler
-                ->schedule([&] {
-                    StringAppendF(&compositionLayers, "Composition layers\n");
-                    mDrawingState.traverseInZOrder([&](Layer* layer) {
-                        auto* compositionState = layer->getCompositionState();
-                        if (!compositionState || !compositionState->isVisible) return;
+                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                    if (!mLayerLifecycleManagerEnabled) {
+                        StringAppendF(&compositionLayers, "Composition layers\n");
+                        mDrawingState.traverseInZOrder([&](Layer* layer) {
+                            auto* compositionState = layer->getCompositionState();
+                            if (!compositionState || !compositionState->isVisible) return;
+                            android::base::StringAppendF(&compositionLayers, "* Layer %p (%s)\n",
+                                                         layer,
+                                                         layer->getDebugName()
+                                                                 ? layer->getDebugName()
+                                                                 : "<unknown>");
+                            compositionState->dump(compositionLayers);
+                        });
+                    } else {
+                        std::ostringstream out;
+                        out << "\nComposition list\n";
+                        ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+                        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                                    if (snapshot->hasSomethingToDraw()) {
+                                        if (lastPrintedLayerStackHeader !=
+                                            snapshot->outputFilter.layerStack) {
+                                            lastPrintedLayerStackHeader =
+                                                    snapshot->outputFilter.layerStack;
+                                            out << "LayerStack=" << lastPrintedLayerStackHeader.id
+                                                << "\n";
+                                        }
+                                        out << "  " << *snapshot << "\n";
+                                    }
+                                });
 
-                        android::base::StringAppendF(&compositionLayers, "* Layer %p (%s)\n", layer,
-                                                     layer->getDebugName() ? layer->getDebugName()
-                                                                           : "<unknown>");
-                        compositionState->dump(compositionLayers);
-                    });
+                        out << "\nInput list\n";
+                        lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+                        mLayerSnapshotBuilder.forEachInputSnapshot(
+                                [&](const frontend::LayerSnapshot& snapshot) {
+                                    if (lastPrintedLayerStackHeader !=
+                                        snapshot.outputFilter.layerStack) {
+                                        lastPrintedLayerStackHeader =
+                                                snapshot.outputFilter.layerStack;
+                                        out << "LayerStack=" << lastPrintedLayerStackHeader.id
+                                            << "\n";
+                                    }
+                                    out << "  " << snapshot << "\n";
+                                });
+
+                        out << "\nLayer Hierarchy\n"
+                            << mLayerHierarchyBuilder.getHierarchy() << "\n\n";
+                        compositionLayers = out.str();
+                        dumpHwcLayersMinidump(compositionLayers);
+                    }
                 })
                 .get();
 
@@ -5679,9 +5988,10 @@
         }
 
         if (dumpLayers) {
-            LayersTraceFileProto traceFileProto = mLayerTracing.createTraceFileProto();
-            LayersTraceProto* layersTrace = traceFileProto.add_entry();
-            LayersProto layersProto = dumpProtoFromMainThread();
+            perfetto::protos::LayersTraceFileProto traceFileProto =
+                    mLayerTracing.createTraceFileProto();
+            perfetto::protos::LayersSnapshotProto* layersTrace = traceFileProto.add_entry();
+            perfetto::protos::LayersProto layersProto = dumpProtoFromMainThread();
             layersTrace->mutable_layers()->Swap(&layersProto);
             auto displayProtos = dumpDisplayProto();
             layersTrace->mutable_displays()->Swap(&displayProtos);
@@ -5697,6 +6007,7 @@
             }
         }
     }
+
     write(fd, result.c_str(), result.size());
     return NO_ERROR;
 }
@@ -5879,7 +6190,6 @@
 
 void SurfaceFlinger::dumpWideColorInfo(std::string& result) const {
     StringAppendF(&result, "Device supports wide color: %d\n", mSupportsWideColor);
-    StringAppendF(&result, "Device uses color management: %d\n", useColorManagement);
     StringAppendF(&result, "DisplayColorSetting: %s\n",
                   decodeDisplayColorSetting(mDisplayColorSetting).c_str());
 
@@ -5900,7 +6210,47 @@
     result.append("\n");
 }
 
-LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
+void SurfaceFlinger::dumpHdrInfo(std::string& result) const {
+    for (const auto& [displayId, listener] : mHdrLayerInfoListeners) {
+        StringAppendF(&result, "HDR events for display %" PRIu64 "\n", displayId.value);
+        listener->dump(result);
+        result.append("\n");
+    }
+}
+
+void SurfaceFlinger::dumpFrontEnd(std::string& result) {
+    mScheduler
+            ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                std::ostringstream out;
+                out << "\nComposition list\n";
+                ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+                for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+                    if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+                        lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+                        out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+                    }
+                    out << "  " << *snapshot << "\n";
+                }
+
+                out << "\nInput list\n";
+                lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+                mLayerSnapshotBuilder.forEachInputSnapshot(
+                        [&](const frontend::LayerSnapshot& snapshot) {
+                            if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+                                lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+                                out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+                            }
+                            out << "  " << snapshot << "\n";
+                        });
+
+                out << "\nLayer Hierarchy\n"
+                    << mLayerHierarchyBuilder.getHierarchy().dump() << "\n\n";
+                result.append(out.str());
+            })
+            .get();
+}
+
+perfetto::protos::LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
     std::unordered_set<uint64_t> stackIdsToSkip;
 
     // Determine if virtual layers display should be skipped
@@ -5913,7 +6263,7 @@
     }
 
     if (mLegacyFrontEndEnabled) {
-        LayersProto layersProto;
+        perfetto::protos::LayersProto layersProto;
         for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
             if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
                 continue;
@@ -5928,13 +6278,21 @@
             .generate(mLayerHierarchyBuilder.getHierarchy());
 }
 
-google::protobuf::RepeatedPtrField<DisplayProto> SurfaceFlinger::dumpDisplayProto() const {
-    google::protobuf::RepeatedPtrField<DisplayProto> displays;
+google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
+SurfaceFlinger::dumpDisplayProto() const {
+    google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> displays;
     for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
-        DisplayProto* displayProto = displays.Add();
+        perfetto::protos::DisplayProto* displayProto = displays.Add();
         displayProto->set_id(display->getId().value);
         displayProto->set_name(display->getDisplayName());
         displayProto->set_layer_stack(display->getLayerStack().id);
+
+        if (!display->isVirtual()) {
+            const auto dpi = display->refreshRateSelector().getActiveMode().modePtr->getDpi();
+            displayProto->set_dpi_x(dpi.x);
+            displayProto->set_dpi_y(dpi.y);
+        }
+
         LayerProtoHelper::writeSizeToProto(display->getWidth(), display->getHeight(),
                                            [&]() { return displayProto->mutable_size(); });
         LayerProtoHelper::writeToProto(display->getLayerStackSpaceRect(), [&]() {
@@ -5951,10 +6309,11 @@
     getHwComposer().dump(result);
 }
 
-void SurfaceFlinger::dumpOffscreenLayersProto(LayersProto& layersProto, uint32_t traceFlags) const {
+void SurfaceFlinger::dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
+                                              uint32_t traceFlags) const {
     // Add a fake invisible root layer to the proto output and parent all the offscreen layers to
     // it.
-    LayerProto* rootProto = layersProto.add_layers();
+    perfetto::protos::LayerProto* rootProto = layersProto.add_layers();
     const int32_t offscreenRootLayerId = INT32_MAX - 2;
     rootProto->set_id(offscreenRootLayerId);
     rootProto->set_name("Offscreen Root");
@@ -5965,12 +6324,12 @@
         rootProto->add_children(offscreenLayer->sequence);
 
         // Add layer
-        LayerProto* layerProto = offscreenLayer->writeToProto(layersProto, traceFlags);
+        auto* layerProto = offscreenLayer->writeToProto(layersProto, traceFlags);
         layerProto->set_parent(offscreenRootLayerId);
     }
 }
 
-LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
+perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
     return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
 }
 
@@ -5988,7 +6347,7 @@
     result.append(future.get());
 }
 
-void SurfaceFlinger::dumpHwcLayersMinidumpLocked(std::string& result) const {
+void SurfaceFlinger::dumpHwcLayersMinidumpLockedLegacy(std::string& result) const {
     for (const auto& [token, display] : mDisplays) {
         const auto displayId = HalDisplayId::tryCast(display->getId());
         if (!displayId) {
@@ -6000,7 +6359,33 @@
         Layer::miniDumpHeader(result);
 
         const DisplayDevice& ref = *display;
-        mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); });
+        mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDumpLegacy(result, ref); });
+        result.append("\n");
+    }
+}
+
+void SurfaceFlinger::dumpHwcLayersMinidump(std::string& result) const {
+    for (const auto& [token, display] : mDisplays) {
+        const auto displayId = HalDisplayId::tryCast(display->getId());
+        if (!displayId) {
+            continue;
+        }
+
+        StringAppendF(&result, "Display %s (%s) HWC layers:\n", to_string(*displayId).c_str(),
+                      displayId == mActiveDisplayId ? "active" : "inactive");
+        Layer::miniDumpHeader(result);
+
+        const DisplayDevice& ref = *display;
+        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+            if (!snapshot.hasSomethingToDraw() ||
+                ref.getLayerStack() != snapshot.outputFilter.layerStack) {
+                return;
+            }
+            auto it = mLegacyLayers.find(snapshot.sequence);
+            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
+                                snapshot.getDebugString().c_str());
+            it->second->miniDump(result, snapshot, ref);
+        });
         result.append("\n");
     }
 }
@@ -6031,6 +6416,8 @@
     result.append("\nWide-Color information:\n");
     dumpWideColorInfo(result);
 
+    dumpHdrInfo(result);
+
     colorizer.bold(result);
     result.append("Sync configuration: ");
     colorizer.reset(result);
@@ -6049,7 +6436,10 @@
      * Dump the visible layer list
      */
     colorizer.bold(result);
-    StringAppendF(&result, "Visible layers (count = %zu)\n", mNumLayers.load());
+    StringAppendF(&result, "SurfaceFlinger New Frontend Enabled:%s\n",
+                  mLayerLifecycleManagerEnabled ? "true" : "false");
+    StringAppendF(&result, "Active Layers - layers with client handles (count = %zu)\n",
+                  mNumLayers.load());
     colorizer.reset(result);
 
     result.append(compositionLayers);
@@ -6071,6 +6461,17 @@
     result.append("SurfaceFlinger global state:\n");
     colorizer.reset(result);
 
+    StringAppendF(&result, "MiscFlagValue: %s\n", mMiscFlagValue ? "true" : "false");
+    StringAppendF(&result, "ConnectedDisplayFlagValue: %s\n",
+                  mConnectedDisplayFlagValue ? "true" : "false");
+    StringAppendF(&result, "Misc2FlagValue: %s (%s after boot)\n",
+                  mMisc2FlagLateBootValue ? "true" : "false",
+                  mMisc2FlagEarlyBootValue == mMisc2FlagLateBootValue ? "stable" : "modified");
+    StringAppendF(&result, "VrrConfigFlagValue: %s\n",
+                  flagutils::vrrConfigEnabled() ? "true" : "false");
+    StringAppendF(&result, "DontSkipOnEarlyFlagValue: %s\n",
+                  flags::dont_skip_on_early() ? "true" : "false");
+
     getRenderEngine().dump(result);
 
     result.append("ClientCache state:\n");
@@ -6089,7 +6490,7 @@
         std::string fps, xDpi, yDpi;
         if (const auto activeModePtr =
                     display->refreshRateSelector().getActiveMode().modePtr.get()) {
-            fps = to_string(activeModePtr->getFps());
+            fps = to_string(activeModePtr->getVsyncRate());
 
             const auto dpi = activeModePtr->getDpi();
             xDpi = base::StringPrintf("%.2f", dpi.x);
@@ -6108,11 +6509,6 @@
 
     StringAppendF(&result, "  transaction time: %f us\n", inTransactionDuration / 1000.0);
 
-    /*
-     * Tracing state
-     */
-    mLayerTracing.dump(result);
-
     result.append("\nTransaction tracing: ");
     if (mTransactionTracing) {
         result.append("enabled\n");
@@ -6122,7 +6518,9 @@
     }
     result.push_back('\n');
 
-    dumpHwcLayersMinidumpLocked(result);
+    if (mLegacyFrontEndEnabled) {
+        dumpHwcLayersMinidumpLockedLegacy(result);
+    }
 
     {
         DumpArgs plannerArgs;
@@ -6218,6 +6616,7 @@
         case GET_ACTIVE_DISPLAY_MODE:
         case GET_DISPLAY_COLOR_MODES:
         case GET_DISPLAY_MODES:
+        case GET_SCHEDULING_POLICY:
         // Calling setTransactionState is safe, because you need to have been
         // granted a reference to Client* and Handle* to do anything with it.
         case SET_TRANSACTION_STATE: {
@@ -6299,9 +6698,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1042 are currently used for backdoors. The code
+    // Numbers from 1000 to 1045 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1042) {
+    if (code >= 1000 && code <= 1045) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -6334,21 +6733,14 @@
             case 1001:
                 return NAME_NOT_FOUND;
             case 1002: // Toggle flashing on surface damage.
-                if (const int delay = data.readInt32(); delay > 0) {
-                    mDebugFlashDelay = delay;
-                } else {
-                    mDebugFlashDelay = mDebugFlashDelay ? 0 : 1;
-                }
-                scheduleRepaint();
+                sfdo_setDebugFlash(data.readInt32());
                 return NO_ERROR;
             case 1004: // Force composite ahead of next VSYNC.
             case 1006:
-                scheduleComposite(FrameHint::kActive);
+                sfdo_scheduleComposite();
                 return NO_ERROR;
             case 1005: { // Force commit ahead of next VSYNC.
-                Mutex::Autolock lock(mStateLock);
-                setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded |
-                                    eTraversalNeeded);
+                sfdo_scheduleCommit();
                 return NO_ERROR;
             }
             case 1007: // Unused.
@@ -6469,42 +6861,13 @@
             case 1024: {
                 return NAME_NOT_FOUND;
             }
-            case 1025: { // Set layer tracing
-                n = data.readInt32();
-                bool tracingEnabledChanged;
-                if (n == 1) {
-                    int64_t fixedStartingTime = data.readInt64();
-                    ALOGD("LayerTracing enabled");
-                    tracingEnabledChanged = mLayerTracing.enable();
-                    if (tracingEnabledChanged) {
-                        const TimePoint startingTime = fixedStartingTime
-                                ? TimePoint::fromNs(fixedStartingTime)
-                                : TimePoint::now();
-
-                        mScheduler
-                                ->schedule([this, startingTime]() FTL_FAKE_GUARD(
-                                                   mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                                    constexpr bool kVisibleRegionDirty = true;
-                                    addToLayerTracing(kVisibleRegionDirty, startingTime,
-                                                      mLastCommittedVsyncId);
-                                })
-                                .wait();
-                    }
-                } else if (n == 2) {
-                    std::string filename = std::string(data.readCString());
-                    ALOGD("LayerTracing disabled. Trace wrote to %s", filename.c_str());
-                    tracingEnabledChanged = mLayerTracing.disable(filename.c_str());
-                } else {
-                    ALOGD("LayerTracing disabled");
-                    tracingEnabledChanged = mLayerTracing.disable();
-                }
-                mTracingEnabledChanged = tracingEnabledChanged;
-                reply->writeInt32(NO_ERROR);
-                return NO_ERROR;
+            // Deprecated, use perfetto to start/stop the layer tracing
+            case 1025: {
+                return NAME_NOT_FOUND;
             }
-            case 1026: { // Get layer tracing status
-                reply->writeBool(mLayerTracing.isEnabled());
-                return NO_ERROR;
+            // Deprecated, execute "adb shell perfetto --query" to see the ongoing tracing sessions
+            case 1026: {
+                return NAME_NOT_FOUND;
             }
             // Is a DisplayColorSetting supported?
             case 1027: {
@@ -6516,8 +6879,6 @@
                 DisplayColorSetting setting = static_cast<DisplayColorSetting>(data.readInt32());
                 switch (setting) {
                     case DisplayColorSetting::kManaged:
-                        reply->writeBool(useColorManagement);
-                        break;
                     case DisplayColorSetting::kUnmanaged:
                         reply->writeBool(true);
                         break;
@@ -6534,23 +6895,14 @@
             case 1028: { // Unused.
                 return NAME_NOT_FOUND;
             }
-            // Set buffer size for SF tracing (value in KB)
+            // Deprecated, use perfetto to set the active layer tracing buffer size
             case 1029: {
-                n = data.readInt32();
-                if (n <= 0 || n > MAX_TRACING_MEMORY) {
-                    ALOGW("Invalid buffer size: %d KB", n);
-                    reply->writeInt32(BAD_VALUE);
-                    return BAD_VALUE;
-                }
-
-                ALOGD("Updating trace buffer to %d KB", n);
-                mLayerTracing.setBufferSize(n * 1024);
-                reply->writeInt32(NO_ERROR);
-                return NO_ERROR;
+                return NAME_NOT_FOUND;
             }
             // Is device color managed?
             case 1030: {
-                reply->writeBool(useColorManagement);
+                // ColorDisplayManager stil calls this
+                reply->writeBool(true);
                 return NO_ERROR;
             }
             // Override default composition data space
@@ -6585,31 +6937,27 @@
                 }
                 return NO_ERROR;
             }
-            // Set trace flags
+            // Deprecated, use perfetto to set layer trace flags
             case 1033: {
-                n = data.readUint32();
-                ALOGD("Updating trace flags to 0x%x", n);
-                mLayerTracing.setTraceFlags(n);
-                reply->writeInt32(NO_ERROR);
-                return NO_ERROR;
+                return NAME_NOT_FOUND;
             }
             case 1034: {
-                auto future = mScheduler->schedule(
-                        [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                            switch (n = data.readInt32()) {
-                                case 0:
-                                case 1:
-                                    enableRefreshRateOverlay(static_cast<bool>(n));
-                                    break;
-                                default:
-                                    reply->writeBool(isRefreshRateOverlayEnabled());
-                            }
-                        });
-
-                future.wait();
+                n = data.readInt32();
+                if (n == 0 || n == 1) {
+                    sfdo_enableRefreshRateOverlay(static_cast<bool>(n));
+                } else {
+                    Mutex::Autolock lock(mStateLock);
+                    reply->writeBool(isRefreshRateOverlayEnabled());
+                }
                 return NO_ERROR;
             }
             case 1035: {
+                // Parameters:
+                // - (required) i32 mode id.
+                // - (optional) i64 display id. Using default display if not provided.
+                // - (optional) f min render rate. Using mode's fps is not provided.
+                // - (optional) f max render rate. Using mode's fps is not provided.
+
                 const int modeId = data.readInt32();
 
                 const auto display = [&]() -> sp<IBinder> {
@@ -6626,8 +6974,21 @@
                     return nullptr;
                 }();
 
+                const auto getFps = [&] {
+                    float value;
+                    if (data.readFloat(&value) == NO_ERROR) {
+                        return Fps::fromValue(value);
+                    }
+
+                    return Fps();
+                };
+
+                const auto minFps = getFps();
+                const auto maxFps = getFps();
+
                 mDebugDisplayModeSetByBackdoor = false;
-                const status_t result = setActiveModeFromBackdoor(display, DisplayModeId{modeId});
+                const status_t result =
+                        setActiveModeFromBackdoor(display, DisplayModeId{modeId}, minFps, maxFps);
                 mDebugDisplayModeSetByBackdoor = result == NO_ERROR;
                 return result;
             }
@@ -6738,11 +7099,14 @@
             }
             case 1041: { // Transaction tracing
                 if (mTransactionTracing) {
-                    if (data.readInt32()) {
+                    int arg = data.readInt32();
+                    if (arg == -1) {
+                        mScheduler->schedule([&]() { mTransactionTracing.reset(); }).get();
+                    } else if (arg > 0) {
                         // Transaction tracing is always running but allow the user to temporarily
                         // increase the buffer when actively debugging.
                         mTransactionTracing->setBufferSize(
-                                TransactionTracing::ACTIVE_TRACING_BUFFER_SIZE);
+                                TransactionTracing::LEGACY_ACTIVE_TRACING_BUFFER_SIZE);
                     } else {
                         TransactionTraceWriter::getInstance().invoke("", /* overwrite= */ true);
                         mTransactionTracing->setBufferSize(
@@ -6752,16 +7116,131 @@
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
-            case 1042: { // Write layers trace or transaction trace to file
+            case 1042: { // Write transaction trace to file
                 if (mTransactionTracing) {
                     mTransactionTracing->writeToFile();
                 }
-                if (mLayerTracingEnabled) {
-                    mLayerTracing.writeToFile();
-                }
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
+            // hdr sdr ratio overlay
+            case 1043: {
+                auto future = mScheduler->schedule(
+                        [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                            n = data.readInt32();
+                            mHdrSdrRatioOverlay = n != 0;
+                            switch (n) {
+                                case 0:
+                                case 1:
+                                    enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
+                                    break;
+                                default:
+                                    reply->writeBool(isHdrSdrRatioOverlayEnabled());
+                            }
+                        });
+                future.wait();
+                return NO_ERROR;
+            }
+
+            case 1044: { // Enable/Disable mirroring from one display to another
+                /*
+                 * Mirror one display onto another.
+                 * Ensure the source and destination displays are on.
+                 * Commands:
+                 * 0: Mirror one display to another
+                 * 1: Disable mirroring to a previously mirrored display
+                 * 2: Disable mirroring on previously mirrored displays
+                 *
+                 * Ex:
+                 * Get the display ids:
+                 * adb shell dumpsys SurfaceFlinger --display-id
+                 * Mirror first display to the second:
+                 * adb shell service call SurfaceFlinger 1044 i64 0 i64 4619827677550801152 i64
+                 * 4619827677550801153
+                 * Stop mirroring:
+                 * adb shell service call SurfaceFlinger 1044 i64 1
+                 */
+
+                int64_t arg0 = data.readInt64();
+
+                switch (arg0) {
+                    case 0: {
+                        // Mirror arg1 to arg2
+                        int64_t arg1 = data.readInt64();
+                        int64_t arg2 = data.readInt64();
+                        // Enable mirroring for one display
+                        const auto display1id = DisplayId::fromValue(arg1);
+                        auto mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(
+                                display1id.value());
+                        auto id2 = DisplayId::fromValue<PhysicalDisplayId>(arg2);
+                        const auto token2 = getPhysicalDisplayToken(*id2);
+                        ui::LayerStack layerStack;
+                        {
+                            Mutex::Autolock lock(mStateLock);
+                            sp<DisplayDevice> display = getDisplayDeviceLocked(token2);
+                            layerStack = display->getLayerStack();
+                        }
+                        SurfaceComposerClient::Transaction t;
+                        t.setDisplayLayerStack(token2, layerStack);
+                        t.setLayer(mirrorRoot, INT_MAX); // Top-most layer
+                        t.setLayerStack(mirrorRoot, layerStack);
+                        t.apply();
+
+                        mMirrorMapForDebug.emplace_or_replace(arg2, mirrorRoot);
+                        break;
+                    }
+
+                    case 1: {
+                        // Disable mirroring for arg1
+                        int64_t arg1 = data.readInt64();
+                        mMirrorMapForDebug.erase(arg1);
+                        break;
+                    }
+
+                    case 2: {
+                        // Disable mirroring for all displays
+                        mMirrorMapForDebug.clear();
+                        break;
+                    }
+
+                    default:
+                        return BAD_VALUE;
+                }
+                return NO_ERROR;
+            }
+            // Inject jank
+            // First argument is a float that describes the fraction of frame duration to jank by.
+            // Second argument is a delay in ms for triggering the jank. This is useful for working
+            // with tools that steal the adb connection. This argument is optional.
+            case 1045: {
+                if (flagutils::vrrConfigEnabled()) {
+                    float jankAmount = data.readFloat();
+                    int32_t jankDelayMs = 0;
+                    if (data.readInt32(&jankDelayMs) != NO_ERROR) {
+                        jankDelayMs = 0;
+                    }
+
+                    const auto jankDelayDuration = Duration(std::chrono::milliseconds(jankDelayMs));
+
+                    const bool jankAmountValid = jankAmount > 0.0 && jankAmount < 100.0;
+
+                    if (!jankAmountValid) {
+                        ALOGD("Ignoring invalid jank amount: %f", jankAmount);
+                        reply->writeInt32(BAD_VALUE);
+                        return BAD_VALUE;
+                    }
+
+                    (void)mScheduler->scheduleDelayed(
+                            [&, jankAmount]() FTL_FAKE_GUARD(kMainThreadContext) {
+                                mScheduler->injectPacesetterDelay(jankAmount);
+                                scheduleComposite(FrameHint::kActive);
+                            },
+                            jankDelayDuration.ns());
+                    reply->writeInt32(NO_ERROR);
+                    return NO_ERROR;
+                }
+                return err;
+            }
         }
     }
     return err;
@@ -7003,16 +7482,27 @@
 
 } // namespace
 
-status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
-                                        const sp<IScreenCaptureListener>& captureListener) {
+static void invokeScreenCaptureError(const status_t status,
+                                     const sp<IScreenCaptureListener>& captureListener) {
+    ScreenCaptureResults captureResults;
+    captureResults.fenceResult = base::unexpected(status);
+    captureListener->onScreenCaptureCompleted(captureResults);
+}
+
+void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
+                                    const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
-        return validate;
+        invokeScreenCaptureError(validate, captureListener);
+        return;
     }
 
-    if (!args.displayToken) return BAD_VALUE;
+    if (!args.displayToken) {
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
+    }
 
     wp<const DisplayDevice> displayWeak;
     ui::LayerStack layerStack;
@@ -7021,7 +7511,10 @@
     {
         Mutex::Autolock lock(mStateLock);
         sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
-        if (!display) return NAME_NOT_FOUND;
+        if (!display) {
+            invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+            return;
+        }
         displayWeak = display;
         layerStack = display->getLayerStack();
 
@@ -7036,7 +7529,8 @@
                 excludeLayerIds.emplace(excludeLayer);
             } else {
                 ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay");
-                return NAME_NOT_FOUND;
+                invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+                return;
             }
         }
     }
@@ -7059,14 +7553,12 @@
         getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
     }
 
-    auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize,
-                                      args.pixelFormat, args.allowProtected, args.grayscale,
-                                      captureListener);
-    return fenceStatus(future.get());
+    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
+                        args.allowProtected, args.grayscale, captureListener);
 }
 
-status_t SurfaceFlinger::captureDisplay(DisplayId displayId,
-                                        const sp<IScreenCaptureListener>& captureListener) {
+void SurfaceFlinger::captureDisplay(DisplayId displayId,
+                                    const sp<IScreenCaptureListener>& captureListener) {
     ui::LayerStack layerStack;
     wp<const DisplayDevice> displayWeak;
     ui::Size size;
@@ -7075,7 +7567,8 @@
 
         const auto display = getDisplayDeviceLocked(displayId);
         if (!display) {
-            return NAME_NOT_FOUND;
+            invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+            return;
         }
 
         displayWeak = display;
@@ -7103,25 +7596,25 @@
 
     if (captureListener == nullptr) {
         ALOGE("capture screen must provide a capture listener callback");
-        return BAD_VALUE;
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
     }
 
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
-    auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size,
-                                      ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale,
-                                      captureListener);
-    return fenceStatus(future.get());
+    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size,
+                        ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale, captureListener);
 }
 
-status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
-                                       const sp<IScreenCaptureListener>& captureListener) {
+void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
+                                   const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
-        return validate;
+        invokeScreenCaptureError(validate, captureListener);
+        return;
     }
 
     ui::Size reqSize;
@@ -7139,13 +7632,15 @@
         parent = LayerHandle::getLayer(args.layerHandle);
         if (parent == nullptr) {
             ALOGE("captureLayers called with an invalid or removed parent");
-            return NAME_NOT_FOUND;
+            invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+            return;
         }
 
         if (!canCaptureBlackoutContent &&
             parent->getDrawingState().flags & layer_state_t::eLayerSecure) {
             ALOGW("Attempting to capture secure layer: PERMISSION_DENIED");
-            return PERMISSION_DENIED;
+            invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
+            return;
         }
 
         Rect parentSourceBounds = parent->getCroppedBufferSize(parent->getDrawingState());
@@ -7162,7 +7657,8 @@
         if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) {
             // Error out if the layer has no source bounds (i.e. they are boundless) and a source
             // crop was not specified, or an invalid frame scale was provided.
-            return BAD_VALUE;
+            invokeScreenCaptureError(BAD_VALUE, captureListener);
+            return;
         }
         reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
 
@@ -7172,7 +7668,8 @@
                 excludeLayerIds.emplace(excludeLayer);
             } else {
                 ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
-                return NAME_NOT_FOUND;
+                invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+                return;
             }
         }
     } // mStateLock
@@ -7180,7 +7677,8 @@
     // really small crop or frameScale
     if (reqSize.width <= 0 || reqSize.height <= 0) {
         ALOGW("Failed to captureLayes: crop or scale too small");
-        return BAD_VALUE;
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
     }
 
     bool childrenOnly = args.childrenOnly;
@@ -7244,26 +7742,27 @@
 
     if (captureListener == nullptr) {
         ALOGE("capture screen must provide a capture listener callback");
-        return BAD_VALUE;
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
     }
 
-    auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize,
-                                      args.pixelFormat, args.allowProtected, args.grayscale,
-                                      captureListener);
-    return fenceStatus(future.get());
+    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
+                        args.allowProtected, args.grayscale, captureListener);
 }
 
-ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
-        RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
-        ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale,
-        const sp<IScreenCaptureListener>& captureListener) {
+void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture,
+                                         GetLayerSnapshotsFunction getLayerSnapshots,
+                                         ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
+                                         bool allowProtected, bool grayscale,
+                                         const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
     if (exceedsMaxRenderTargetSize(bufferSize.getWidth(), bufferSize.getHeight())) {
         ALOGE("Attempted to capture screen with size (%" PRId32 ", %" PRId32
               ") that exceeds render target size limit.",
               bufferSize.getWidth(), bufferSize.getHeight());
-        return ftl::yield<FenceResult>(base::unexpected(BAD_VALUE)).share();
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
     }
 
     // Loop over all visible layers to see whether there's any protected layer. A protected layer is
@@ -7303,14 +7802,16 @@
         // Otherwise an irreponsible process may cause an SF crash by allocating
         // too much.
         ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
-        return ftl::yield<FenceResult>(base::unexpected(bufferStatus)).share();
+        invokeScreenCaptureError(bufferStatus, captureListener);
+        return;
     }
     const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
             renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
                                                  renderengine::impl::ExternalTexture::Usage::
                                                          WRITEABLE);
-    return captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
-                               false /* regionSampling */, grayscale, captureListener);
+    auto fence = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
+                                     false /* regionSampling */, grayscale, captureListener);
+    fence.get();
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
@@ -7425,7 +7926,10 @@
                                       renderArea->getHintForSeamlessTransition());
             sdrWhitePointNits = state.sdrWhitePointNits;
 
-            if (!captureResults.capturedHdrLayers) {
+             // TODO(b/298219334): Clean this up once we verify this doesn't break anything
+             static constexpr bool kScreenshotsDontDim = true;
+
+            if (kScreenshotsDontDim && !captureResults.capturedHdrLayers) {
                 displayBrightnessNits = sdrWhitePointNits;
             } else {
                 displayBrightnessNits = state.displayBrightnessNits;
@@ -7529,7 +8033,7 @@
     const bool renderEngineIsThreaded = [&]() {
         using Type = renderengine::RenderEngine::RenderEngineType;
         const auto type = mRenderEngine->getRenderEngineType();
-        return type == Type::THREADED || type == Type::SKIA_GL_THREADED;
+        return type == Type::SKIA_GL_THREADED;
     }();
     auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share()
                                                 : ftl::yield(present()).share();
@@ -7621,7 +8125,7 @@
                 return snapshot.displayModes().get(defaultModeId);
             })
             .transform([](const DisplayModePtr& modePtr) {
-                return scheduler::FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)};
+                return scheduler::FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
             });
 }
 
@@ -7708,7 +8212,13 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredActiveMode({std::move(preferredMode), .emitEvent = true}, force);
+    setDesiredActiveMode({preferredMode, .emitEvent = true}, force);
+
+    // Update the frameRateOverride list as the display render rate might have changed
+    if (mScheduler->updateFrameRateOverrides(/*consideredSignals*/ {}, preferredMode.fps)) {
+        triggerOnFrameRateOverridesChanged();
+    }
+
     return NO_ERROR;
 }
 
@@ -7809,6 +8319,7 @@
     if (mTransactionTracing) {
         mTransactionTracing->onLayerRemoved(layer->getSequence());
     }
+    mScheduler->onLayerDestroyed(layer);
 }
 
 void SurfaceFlinger::onLayerUpdate() {
@@ -7873,13 +8384,13 @@
 }
 
 status_t SurfaceFlinger::updateSmallAreaDetection(
-        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
-    mScheduler->updateSmallAreaDetection(uidThresholdMappings);
+        std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
+    mScheduler->updateSmallAreaDetection(appIdThresholdMappings);
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
-    mScheduler->setSmallAreaDetectionThreshold(uid, threshold);
+status_t SurfaceFlinger::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+    mScheduler->setSmallAreaDetectionThreshold(appId, threshold);
     return NO_ERROR;
 }
 
@@ -7908,6 +8419,16 @@
     }
 }
 
+void SurfaceFlinger::enableHdrSdrRatioOverlay(bool enable) {
+    for (const auto& [id, display] : mPhysicalDisplays) {
+        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+            if (const auto device = getDisplayDeviceLocked(id)) {
+                device->enableHdrSdrRatioOverlay(enable);
+            }
+        }
+    }
+}
+
 int SurfaceFlinger::getGpuContextPriority() {
     return getRenderEngine().getContextPriority();
 }
@@ -7918,7 +8439,7 @@
     if (presentLatency.count() % refreshRate.getPeriodNsecs()) {
         pipelineDepth++;
     }
-    return std::max(1ll, pipelineDepth - 1);
+    return std::max(minAcquiredBuffers, static_cast<int64_t>(pipelineDepth - 1));
 }
 
 status_t SurfaceFlinger::getMaxAcquiredBufferCount(int* buffers) const {
@@ -8010,15 +8531,6 @@
 void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) {
     mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight());
     getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize());
-
-    // Notify layers to update small dirty flag.
-    if (mScheduler->supportSmallDirtyDetection()) {
-        mCurrentState.traverse([&](Layer* layer) {
-            if (layer->getLayerStack() == activeDisplay.getLayerStack()) {
-                layer->setIsSmallDirty();
-            }
-        });
-    }
 }
 
 sp<DisplayDevice> SurfaceFlinger::getActivatableDisplay() const {
@@ -8370,7 +8882,9 @@
                      .excludeLayerIds = std::move(excludeLayerIds),
                      .supportedLayerGenericMetadata =
                              getHwComposer().getSupportedLayerGenericMetadata(),
-                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()};
+                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap(),
+                     .skipRoundCornersWhenProtected =
+                             !getRenderEngine().supportsProtectedContent()};
         mLayerSnapshotBuilder.update(args);
 
         auto getLayerSnapshotsFn =
@@ -8405,7 +8919,9 @@
                      .excludeLayerIds = std::move(excludeLayerIds),
                      .supportedLayerGenericMetadata =
                              getHwComposer().getSupportedLayerGenericMetadata(),
-                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()};
+                     .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap(),
+                     .skipRoundCornersWhenProtected =
+                             !getRenderEngine().supportsProtectedContent()};
         mLayerSnapshotBuilder.update(args);
 
         auto getLayerSnapshotsFn =
@@ -8429,6 +8945,7 @@
     // 2. Transactions and created layers do not share a lock. To prevent applying
     // transactions with layers still in the createdLayer queue, flush the transactions
     // before committing the created layers.
+    mTransactionHandler.collectTransactions();
     update.transactions = mTransactionHandler.flushTransactions();
     {
         // TODO(b/238781169) lockless queue this and keep order.
@@ -8445,19 +8962,77 @@
     return update;
 }
 
-void SurfaceFlinger::addToLayerTracing(bool visibleRegionDirty, TimePoint time, VsyncId vsyncId) {
-    const uint32_t tracingFlags = mLayerTracing.getFlags();
-    LayersProto layers(dumpDrawingStateProto(tracingFlags));
-    if (tracingFlags & LayerTracing::TRACE_EXTRA) {
+void SurfaceFlinger::doActiveLayersTracingIfNeeded(bool isCompositionComputed,
+                                                   bool visibleRegionDirty, TimePoint time,
+                                                   VsyncId vsyncId) {
+    if (!mLayerTracing.isActiveTracingStarted()) {
+        return;
+    }
+    if (isCompositionComputed !=
+        mLayerTracing.isActiveTracingFlagSet(LayerTracing::Flag::TRACE_COMPOSITION)) {
+        return;
+    }
+    if (!visibleRegionDirty &&
+        !mLayerTracing.isActiveTracingFlagSet(LayerTracing::Flag::TRACE_BUFFERS)) {
+        return;
+    }
+    auto snapshot = takeLayersSnapshotProto(mLayerTracing.getActiveTracingFlags(), time, vsyncId,
+                                            visibleRegionDirty);
+    mLayerTracing.addProtoSnapshotToOstream(std::move(snapshot), LayerTracing::Mode::MODE_ACTIVE);
+}
+
+perfetto::protos::LayersSnapshotProto SurfaceFlinger::takeLayersSnapshotProto(
+        uint32_t traceFlags, TimePoint time, VsyncId vsyncId, bool visibleRegionDirty) {
+    ATRACE_CALL();
+    perfetto::protos::LayersSnapshotProto snapshot;
+    snapshot.set_elapsed_realtime_nanos(time.ns());
+    snapshot.set_vsync_id(ftl::to_underlying(vsyncId));
+    snapshot.set_where(visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched");
+    snapshot.set_excludes_composition_state((traceFlags & LayerTracing::Flag::TRACE_COMPOSITION) ==
+                                            0);
+
+    auto layers = dumpDrawingStateProto(traceFlags);
+    if (traceFlags & LayerTracing::Flag::TRACE_EXTRA) {
         dumpOffscreenLayersProto(layers);
     }
-    std::string hwcDump;
-    if (tracingFlags & LayerTracing::TRACE_HWC) {
+    *snapshot.mutable_layers() = std::move(layers);
+
+    if (traceFlags & LayerTracing::Flag::TRACE_HWC) {
+        std::string hwcDump;
         dumpHwc(hwcDump);
+        snapshot.set_hwc_blob(std::move(hwcDump));
     }
-    auto displays = dumpDisplayProto();
-    mLayerTracing.notify(visibleRegionDirty, time.ns(), ftl::to_underlying(vsyncId), &layers,
-                         std::move(hwcDump), &displays);
+
+    *snapshot.mutable_displays() = dumpDisplayProto();
+
+    return snapshot;
+}
+
+// sfdo functions
+
+void SurfaceFlinger::sfdo_enableRefreshRateOverlay(bool active) {
+    auto future = mScheduler->schedule(
+            [&]() FTL_FAKE_GUARD(mStateLock)
+                    FTL_FAKE_GUARD(kMainThreadContext) { enableRefreshRateOverlay(active); });
+    future.wait();
+}
+
+void SurfaceFlinger::sfdo_setDebugFlash(int delay) {
+    if (delay > 0) {
+        mDebugFlashDelay = delay;
+    } else {
+        mDebugFlashDelay = mDebugFlashDelay ? 0 : 1;
+    }
+    scheduleRepaint();
+}
+
+void SurfaceFlinger::sfdo_scheduleComposite() {
+    scheduleComposite(SurfaceFlinger::FrameHint::kActive);
+}
+
+void SurfaceFlinger::sfdo_scheduleCommit() {
+    Mutex::Autolock lock(mStateLock);
+    setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded | eTraversalNeeded);
 }
 
 // gui::ISurfaceComposer
@@ -8489,6 +9064,10 @@
     const sp<Client> client = sp<Client>::make(mFlinger);
     if (client->initCheck() == NO_ERROR) {
         *outClient = client;
+        if (flags::misc1()) {
+            const int policy = SCHED_FIFO;
+            client->setMinSchedulerPolicy(policy, sched_get_priority_min(policy));
+        }
         return binder::Status::ok();
     } else {
         *outClient = nullptr;
@@ -8643,6 +9222,7 @@
         outMode.xDpi = mode.xDpi;
         outMode.yDpi = mode.yDpi;
         outMode.refreshRate = mode.refreshRate;
+        outMode.vsyncRate = mode.vsyncRate;
         outMode.appVsyncOffset = mode.appVsyncOffset;
         outMode.sfVsyncOffset = mode.sfVsyncOffset;
         outMode.presentationDeadline = mode.presentationDeadline;
@@ -8812,28 +9392,28 @@
 
 binder::Status SurfaceComposerAIDL::captureDisplay(
         const DisplayCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
-    status_t status = mFlinger->captureDisplay(args, captureListener);
-    return binderStatusFromStatusT(status);
+    mFlinger->captureDisplay(args, captureListener);
+    return binderStatusFromStatusT(NO_ERROR);
 }
 
 binder::Status SurfaceComposerAIDL::captureDisplayById(
         int64_t displayId, const sp<IScreenCaptureListener>& captureListener) {
-    status_t status;
+    // status_t status;
     IPCThreadState* ipc = IPCThreadState::self();
     const int uid = ipc->getCallingUid();
     if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) {
         std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
-        status = mFlinger->captureDisplay(*id, captureListener);
+        mFlinger->captureDisplay(*id, captureListener);
     } else {
-        status = PERMISSION_DENIED;
+        invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
     }
-    return binderStatusFromStatusT(status);
+    return binderStatusFromStatusT(NO_ERROR);
 }
 
 binder::Status SurfaceComposerAIDL::captureLayers(
         const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
-    status_t status = mFlinger->captureLayers(args, captureListener);
-    return binderStatusFromStatusT(status);
+    mFlinger->captureLayers(args, captureListener);
+    return binderStatusFromStatusT(NO_ERROR);
 }
 
 binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp<IBinder>& display,
@@ -8880,11 +9460,6 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::getColorManagement(bool* outGetColorManagement) {
-    status_t status = mFlinger->getColorManagement(outGetColorManagement);
-    return binderStatusFromStatusT(status);
-}
-
 binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) {
     ui::Dataspace dataspace;
     ui::PixelFormat pixelFormat;
@@ -9158,18 +9733,38 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& uids,
+binder::Status SurfaceComposerAIDL::enableRefreshRateOverlay(bool active) {
+    mFlinger->sfdo_enableRefreshRateOverlay(active);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::setDebugFlash(int delay) {
+    mFlinger->sfdo_setDebugFlash(delay);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::scheduleComposite() {
+    mFlinger->sfdo_scheduleComposite();
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::scheduleCommit() {
+    mFlinger->sfdo_scheduleCommit();
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                                              const std::vector<float>& thresholds) {
     status_t status;
     const int c_uid = IPCThreadState::self()->getCallingUid();
     if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
-        if (uids.size() != thresholds.size()) return binderStatusFromStatusT(BAD_VALUE);
+        if (appIds.size() != thresholds.size()) return binderStatusFromStatusT(BAD_VALUE);
 
-        std::vector<std::pair<uid_t, float>> mappings;
-        const size_t size = uids.size();
+        std::vector<std::pair<int32_t, float>> mappings;
+        const size_t size = appIds.size();
         mappings.reserve(size);
         for (int i = 0; i < size; i++) {
-            auto row = std::make_pair(static_cast<uid_t>(uids[i]), thresholds[i]);
+            auto row = std::make_pair(appIds[i], thresholds[i]);
             mappings.push_back(row);
         }
         status = mFlinger->updateSmallAreaDetection(mappings);
@@ -9180,11 +9775,11 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::setSmallAreaDetectionThreshold(int32_t uid, float threshold) {
+binder::Status SurfaceComposerAIDL::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
     status_t status;
     const int c_uid = IPCThreadState::self()->getCallingUid();
     if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
-        status = mFlinger->setSmallAreaDetectionThreshold(uid, threshold);
+        status = mFlinger->setSmallAreaDetectionThreshold(appId, threshold);
     } else {
         ALOGE("setSmallAreaDetectionThreshold() permission denied for uid: %d", c_uid);
         status = PERMISSION_DENIED;
@@ -9255,6 +9850,10 @@
     return binderStatusFromStatusT(status);
 }
 
+binder::Status SurfaceComposerAIDL::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    return gui::getSchedulingPolicy(outPolicy);
+}
+
 status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) {
     if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
         IPCThreadState* ipc = IPCThreadState::self();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 47ada25..96b67b8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -227,14 +227,15 @@
     // FramebufferSurface
     static int64_t maxFrameBufferAcquiredBuffers;
 
+    // Controls the minimum acquired buffers SurfaceFlinger will suggest via
+    // ISurfaceComposer.getMaxAcquiredBufferCount().
+    static int64_t minAcquiredBuffers;
+
     // Controls the maximum width and height in pixels that the graphics pipeline can support for
     // GPU fallback composition. For example, 8k devices with 4k GPUs, or 4k devices with 2k GPUs.
     static uint32_t maxGraphicsWidth;
     static uint32_t maxGraphicsHeight;
 
-    // Indicate if device wants color management on its display.
-    static const constexpr bool useColorManagement = true;
-
     static bool useContextPriority;
 
     // The data space and pixel format that SurfaceFlinger expects hardware composer
@@ -276,13 +277,6 @@
     // The CompositionEngine encapsulates all composition related interfaces and actions.
     compositionengine::CompositionEngine& getCompositionEngine() const;
 
-    // Obtains a name from the texture pool, or, if the pool is empty, posts a
-    // synchronous message to the main thread to obtain one on the fly
-    uint32_t getNewTexture();
-
-    // utility function to delete a texture on the main thread
-    void deleteTextureAsync(uint32_t texture);
-
     renderengine::RenderEngine& getRenderEngine() const;
 
     void onLayerFirstRef(Layer*);
@@ -374,7 +368,6 @@
     friend class RefreshRateOverlay;
     friend class RegionSamplingThread;
     friend class LayerRenderArea;
-    friend class LayerTracing;
     friend class SurfaceComposerAIDL;
     friend class DisplayRenderArea;
 
@@ -426,7 +419,7 @@
         bool colorMatrixChanged = true;
         mat4 colorMatrix;
 
-        renderengine::ShadowSettings globalShadowSettings;
+        ShadowSettings globalShadowSettings;
 
         void traverse(const LayerVector::Visitor& visitor) const;
         void traverseInZOrder(const LayerVector::Visitor& visitor) const;
@@ -536,9 +529,9 @@
             EventRegistrationFlags eventRegistration = {},
             const sp<IBinder>& layerHandle = nullptr);
 
-    status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
-    status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
-    status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+    void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
+    void captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
     status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*)
@@ -568,7 +561,6 @@
                               const std::vector<ui::Hdr>& hdrTypes);
     status_t onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData, bool* success);
     status_t getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers);
-    status_t getColorManagement(bool* outGetColorManagement) const;
     status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
                                       ui::Dataspace* outWideColorGamutDataspace,
                                       ui::PixelFormat* outWideColorGamutPixelFormat) const;
@@ -614,9 +606,9 @@
 
     status_t setOverrideFrameRate(uid_t uid, float frameRate);
 
-    status_t updateSmallAreaDetection(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+    status_t updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
 
-    status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+    status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
 
     int getGpuContextPriority();
 
@@ -659,6 +651,7 @@
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
     void kernelTimerChanged(bool expired) override;
     void triggerOnFrameRateOverridesChanged() override;
+    void onChoreographerAttached() override;
 
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
@@ -685,11 +678,14 @@
     bool mRefreshRateOverlayRenderRate = false;
     // Show render rate overlay offseted to the middle of the screen (e.g. for circular displays)
     bool mRefreshRateOverlayShowInMiddle = false;
+    // Show hdr sdr ratio overlay
+    bool mHdrSdrRatioOverlay = false;
 
     void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
             REQUIRES(mStateLock);
 
-    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
+    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
+                                       Fps maxFps);
 
     void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
     void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
@@ -733,12 +729,13 @@
             compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly);
     void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
                                           const std::vector<std::pair<Layer*, LayerFE*>>& layers);
-    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update,
-                                    bool transactionsFlushed, bool& out)
-            REQUIRES(kMainThreadContext);
-    bool updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update, bool transactionsFlushed,
+    // Return true if we must composite this frame
+    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
+                                    bool& out) REQUIRES(kMainThreadContext);
+    // Return true if we must composite this frame
+    bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                               bool& out) REQUIRES(kMainThreadContext);
-    void updateLayerHistory(const frontend::LayerSnapshot& snapshot);
+    void updateLayerHistory(nsecs_t now);
     frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
     void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime);
@@ -780,6 +777,9 @@
     TransactionHandler::TransactionReadiness transactionReadyTimelineCheck(
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
+    TransactionHandler::TransactionReadiness transactionReadyBufferCheckLegacy(
+            const TransactionHandler::TransactionFlushState& flushState)
+            REQUIRES(kMainThreadContext);
     TransactionHandler::TransactionReadiness transactionReadyBufferCheck(
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
@@ -804,8 +804,7 @@
     void commitOffscreenLayers();
 
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
-    bool shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t&, size_t numStates,
-                               bool firstTransaction) const;
+    bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
     bool applyTransactionsLocked(std::vector<TransactionState>& transactions, VsyncId)
             REQUIRES(mStateLock);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
@@ -843,10 +842,9 @@
     // Boot animation, on/off animations and screen capture
     void startBootAnim();
 
-    ftl::SharedFuture<FenceResult> captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction,
-                                                       ui::Size bufferSize, ui::PixelFormat,
-                                                       bool allowProtected, bool grayscale,
-                                                       const sp<IScreenCaptureListener>&);
+    void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
+                             ui::PixelFormat, bool allowProtected, bool grayscale,
+                             const sp<IScreenCaptureListener>&);
     ftl::SharedFuture<FenceResult> captureScreenCommon(
             RenderAreaFuture, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
@@ -1077,7 +1075,8 @@
      */
     void dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
                        std::string& result) const REQUIRES(mStateLock);
-    void dumpHwcLayersMinidumpLocked(std::string& result) const REQUIRES(mStateLock);
+    void dumpHwcLayersMinidump(std::string& result) const REQUIRES(mStateLock, kMainThreadContext);
+    void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
 
     void appendSfConfigString(std::string& result) const;
     void listLayersLocked(std::string& result) const;
@@ -1096,18 +1095,23 @@
     void dumpDisplayIdentificationData(std::string& result) const REQUIRES(mStateLock);
     void dumpRawDisplayIdentificationData(const DumpArgs&, std::string& result) const;
     void dumpWideColorInfo(std::string& result) const REQUIRES(mStateLock);
+    void dumpHdrInfo(std::string& result) const REQUIRES(mStateLock);
+    void dumpFrontEnd(std::string& result);
 
-    LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
-    void dumpOffscreenLayersProto(LayersProto& layersProto,
+    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
+    void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
                                   uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
-    google::protobuf::RepeatedPtrField<DisplayProto> dumpDisplayProto() const;
-    void addToLayerTracing(bool visibleRegionDirty, TimePoint, VsyncId)
+    google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const;
+    void doActiveLayersTracingIfNeeded(bool isCompositionComputed, bool visibleRegionDirty,
+                                       TimePoint, VsyncId) REQUIRES(kMainThreadContext);
+    perfetto::protos::LayersSnapshotProto takeLayersSnapshotProto(uint32_t flags, TimePoint,
+                                                                  VsyncId, bool visibleRegionDirty)
             REQUIRES(kMainThreadContext);
 
     // Dumps state from HW Composer
     void dumpHwc(std::string& result) const;
-    LayersProto dumpProtoFromMainThread(uint32_t traceFlags = LayerTracing::TRACE_ALL)
-            EXCLUDES(mStateLock);
+    perfetto::protos::LayersProto dumpProtoFromMainThread(
+            uint32_t traceFlags = LayerTracing::TRACE_ALL) EXCLUDES(mStateLock);
     void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
     void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
 
@@ -1143,7 +1147,7 @@
     ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
             REQUIRES(mStateLock);
     void traverseLegacyLayers(const LayerVector::Visitor& visitor) const;
-
+    void initTransactionTraceWriter();
     sp<StartPropertySetThread> mStartPropertySetThread;
     surfaceflinger::Factory& mFactory;
     pid_t mPid;
@@ -1208,6 +1212,7 @@
     bool mUpdateInputInfo = false;
     bool mSomeChildrenChanged;
     bool mForceTransactionDisplayChange = false;
+    bool mUpdateAttachedChoreographer = false;
 
     // Set if LayerMetadata has changed since the last LayerMetadata snapshot.
     bool mLayerMetadataSnapshotNeeded = false;
@@ -1217,6 +1222,8 @@
     // latched.
     std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
     std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithBuffersRemoved;
+    std::unordered_set<uint32_t> mLayersIdsWithQueuedFrames;
+
     // Tracks layers that need to update a display's dirty region.
     std::vector<sp<Layer>> mLayersPendingRefresh;
     // Sorted list of layers that were composed during previous frame. This is used to
@@ -1232,6 +1239,8 @@
         hal::Connection connection = hal::Connection::INVALID;
     };
 
+    bool mIsHotplugErrViaNegVsync = false;
+
     std::mutex mHotplugMutex;
     std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mHotplugMutex);
 
@@ -1263,10 +1272,7 @@
     bool mBackpressureGpuComposition = false;
 
     LayerTracing mLayerTracing;
-    bool mLayerTracingEnabled = false;
-
     std::optional<TransactionTracing> mTransactionTracing;
-    std::atomic<bool> mTracingEnabledChanged = false;
 
     const std::shared_ptr<TimeStats> mTimeStats;
     const std::unique_ptr<FrameTracer> mFrameTracer;
@@ -1279,13 +1285,6 @@
 
     TransactionCallbackInvoker mTransactionCallbackInvoker;
 
-    // We maintain a pool of pre-generated texture names to hand out to avoid
-    // layer creation needing to run on the main thread (which it would
-    // otherwise need to do to access RenderEngine).
-    std::mutex mTexturePoolMutex;
-    uint32_t mTexturePoolSize = 0;
-    std::vector<uint32_t> mTexturePool;
-
     std::atomic<size_t> mNumLayers = 0;
 
     // to linkToDeath
@@ -1314,8 +1313,6 @@
 
     ui::Dataspace mDefaultCompositionDataspace;
     ui::Dataspace mWideColorGamutCompositionDataspace;
-    ui::Dataspace mColorSpaceAgnosticDataspace;
-    float mDimmingRatio = -1.f;
 
     std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
     std::atomic<int> mNumTrustedPresentationListeners = 0;
@@ -1360,6 +1357,8 @@
 
     void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
 
+    void enableHdrSdrRatioOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
+
     // Flag used to set override desired display mode from backdoor
     bool mDebugDisplayModeSetByBackdoor = false;
 
@@ -1405,6 +1404,10 @@
         return hasDisplay(
                 [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
     }
+    bool isHdrSdrRatioOverlayEnabled() const REQUIRES(mStateLock) {
+        return hasDisplay(
+                [](const auto& display) { return display.isHdrSdrRatioOverlayEnabled(); });
+    }
     std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
             std::optional<ui::LayerStack> layerStack, uint32_t uid,
             std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
@@ -1434,7 +1437,7 @@
     frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
     frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
 
-    std::vector<uint32_t> mDestroyedHandles;
+    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
     std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
     std::vector<LayerCreationArgs> mNewLayerArgs;
     // These classes do not store any client state but help with managing transaction callbacks
@@ -1447,6 +1450,21 @@
 
     // WindowInfo ids visible during the last commit.
     std::unordered_set<int32_t> mVisibleWindowIds;
+
+    // Mirroring
+    // Map of displayid to mirrorRoot
+    ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
+
+    void sfdo_enableRefreshRateOverlay(bool active);
+    void sfdo_setDebugFlash(int delay);
+    void sfdo_scheduleComposite();
+    void sfdo_scheduleCommit();
+
+    // Trunk-Stable flags
+    bool mMiscFlagValue;
+    bool mConnectedDisplayFlagValue;
+    bool mMisc2FlagEarlyBootValue;
+    bool mMisc2FlagLateBootValue;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1508,7 +1526,6 @@
                                     const std::vector<int32_t>& hdrTypes) override;
     binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override;
     binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) override;
-    binder::Status getColorManagement(bool* outGetColorManagement) override;
     binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override;
     binder::Status getDisplayedContentSamplingAttributes(
             const sp<IBinder>& display, gui::ContentSamplingAttributes* outAttrs) override;
@@ -1554,17 +1571,22 @@
             const sp<IBinder>& displayToken,
             std::optional<gui::DisplayDecorationSupport>* outSupport) override;
     binder::Status setOverrideFrameRate(int32_t uid, float frameRate) override;
-    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& uids,
+    binder::Status enableRefreshRateOverlay(bool active) override;
+    binder::Status setDebugFlash(int delay) override;
+    binder::Status scheduleComposite() override;
+    binder::Status scheduleCommit() override;
+    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                             const std::vector<float>& thresholds) override;
-    binder::Status setSmallAreaDetectionThreshold(int32_t uid, float threshold) override;
+    binder::Status setSmallAreaDetectionThreshold(int32_t appId, float threshold) override;
     binder::Status getGpuContextPriority(int32_t* outPriority) override;
     binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override;
     binder::Status addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener,
                                           gui::WindowInfosListenerInfo* outInfo) override;
     binder::Status removeWindowInfosListener(
             const sp<gui::IWindowInfosListener>& windowInfosListener) override;
-    binder::Status getStalledTransactionInfo(int pid,
-                                             std::optional<gui::StalledTransactionInfo>* outInfo);
+    binder::Status getStalledTransactionInfo(
+            int pid, std::optional<gui::StalledTransactionInfo>* outInfo) override;
+    binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
 
 private:
     static const constexpr bool kUsePermissionCache = true;
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index 96c8b54..66c8f33 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -227,14 +227,6 @@
     return static_cast<int32_t>(defaultValue);
 }
 
-int64_t color_space_agnostic_dataspace(Dataspace defaultValue) {
-    auto temp = SurfaceFlingerProperties::color_space_agnostic_dataspace();
-    if (temp.has_value()) {
-        return *temp;
-    }
-    return static_cast<int64_t>(defaultValue);
-}
-
 bool refresh_rate_switching(bool defaultValue) {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 951f8f8..a080420 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -71,9 +71,6 @@
 int32_t wcg_composition_pixel_format(
         android::hardware::graphics::common::V1_2::PixelFormat defaultValue);
 
-int64_t color_space_agnostic_dataspace(
-        android::hardware::graphics::common::V1_2::Dataspace defaultValue);
-
 bool refresh_rate_switching(bool defaultValue);
 
 int32_t set_idle_timer_ms(int32_t defaultValue);
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index fc6c4f3..f339d22 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/native/libs/gui"
+    },
+    {
+      "path": "frameworks/native/services/inputflinger"
     }
   ],
   "presubmit": [
@@ -13,11 +16,34 @@
     },
     {
       "name": "libscheduler_test"
+    },
+    {
+      "name": "CtsGraphicsTestCases",
+      // flaky on mixed gsi builds
+      "options": [
+        {
+          "exclude-filter": "android.graphics.cts.CameraGpuTest#testCameraImageCaptureAndRendering"
+        },
+        {
+          "exclude-filter": "android.graphics.cts.AnimatorLeakTest#testPauseResume"
+        }
+      ]
+    },
+    {
+      "name": "CtsSurfaceControlTests"
     }
   ],
   "hwasan-presubmit": [
     {
       "name": "libscheduler_test"
+    },
+    {
+      "name": "libsurfaceflinger_unittest"
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsSurfaceControlTestsStaging"
     }
   ]
 }
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index 4686eed..c3141be 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -7,14 +7,9 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library {
-    name: "libtimestats",
-    srcs: [
-        "TimeStats.cpp",
-    ],
-    header_libs: [
-        "libscheduler_headers",
-    ],
+cc_defaults {
+    name: "libtimestats_deps",
+
     shared_libs: [
         "android.hardware.graphics.composer@2.4",
         "libbase",
@@ -22,17 +17,34 @@
         "liblog",
         "libprotobuf-cpp-lite",
         "libtimestats_atoms_proto",
-        "libtimestats_proto",
         "libui",
         "libutils",
     ],
+
+    static_libs: [
+        "libtimestats_proto",
+    ],
+
+    export_static_lib_headers: [
+        "libtimestats_proto",
+    ],
+}
+
+cc_library {
+    name: "libtimestats",
+    defaults: [
+        "libtimestats_deps",
+    ],
+    srcs: [
+        "TimeStats.cpp",
+    ],
+    header_libs: [
+        "libscheduler_headers",
+    ],
     export_include_dirs: ["."],
     export_header_lib_headers: [
         "libscheduler_headers",
     ],
-    export_shared_lib_headers: [
-        "libtimestats_proto",
-    ],
     cppflags: [
         "-Wall",
         "-Werror",
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 630cef1..368cb41 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -106,7 +106,8 @@
         atom->set_client_composition_frames(mTimeStats.clientCompositionFramesLegacy);
         atom->set_display_on_millis(mTimeStats.displayOnTimeLegacy);
         atom->set_animation_millis(mTimeStats.presentToPresentLegacy.totalTime());
-        atom->set_event_connection_count(mTimeStats.displayEventConnectionsCountLegacy);
+        // Deprecated
+        atom->set_event_connection_count(0);
         *atom->mutable_frame_duration() =
                 histogramToProto(mTimeStats.frameDurationLegacy.hist, mMaxPulledHistogramBuckets);
         *atom->mutable_render_engine_timing() =
@@ -356,16 +357,6 @@
     mTimeStats.refreshRateSwitchesLegacy++;
 }
 
-void TimeStats::recordDisplayEventConnectionCount(int32_t count) {
-    if (!mEnabled.load()) return;
-
-    ATRACE_CALL();
-
-    std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.displayEventConnectionsCountLegacy =
-            std::max(mTimeStats.displayEventConnectionsCountLegacy, count);
-}
-
 static int32_t toMs(nsecs_t nanos) {
     int64_t millis =
             std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds(nanos))
@@ -1072,7 +1063,6 @@
     mTimeStats.compositionStrategyPredictedLegacy = 0;
     mTimeStats.compositionStrategyPredictionSucceededLegacy = 0;
     mTimeStats.refreshRateSwitchesLegacy = 0;
-    mTimeStats.displayEventConnectionsCountLegacy = 0;
     mTimeStats.displayOnTimeLegacy = 0;
     mTimeStats.presentToPresentLegacy.hist.clear();
     mTimeStats.frameDurationLegacy.hist.clear();
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 5f58657..0c227d4 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -57,9 +57,6 @@
     virtual void incrementMissedFrames() = 0;
     // Increments the number of times the display refresh rate changed.
     virtual void incrementRefreshRateSwitches() = 0;
-    // Records the most up-to-date count of display event connections.
-    // The stored count will be the maximum ever recoded.
-    virtual void recordDisplayEventConnectionCount(int32_t count) = 0;
 
     // Records the start and end times for a frame.
     // The start time is the same as the beginning of a SurfaceFlinger
@@ -253,7 +250,6 @@
     void incrementTotalFrames() override;
     void incrementMissedFrames() override;
     void incrementRefreshRateSwitches() override;
-    void recordDisplayEventConnectionCount(int32_t count) override;
 
     void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) override;
     void recordRenderEngineDuration(nsecs_t startTime, nsecs_t endTime) override;
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index cf1ca65..cbbcb91 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -115,7 +115,7 @@
     StringAppendF(&result, "badDesiredPresentFrames = %d\n", badDesiredPresentFrames);
     result.append("Jank payload for this layer:\n");
     result.append(jankPayload.toString());
-    result.append("SetFrateRate vote for this layer:\n");
+    result.append("SetFrameRate vote for this layer:\n");
     result.append(setFrameRateVote.toString());
     const auto iter = deltas.find("present2present");
     if (iter != deltas.end()) {
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 60aa810..9e97f0d 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -175,7 +175,6 @@
         int32_t clientCompositionReusedFramesLegacy = 0;
         int32_t refreshRateSwitchesLegacy = 0;
         int32_t compositionStrategyChangesLegacy = 0;
-        int32_t displayEventConnectionsCountLegacy = 0;
         int64_t displayOnTimeLegacy = 0;
         Histogram presentToPresentLegacy;
         Histogram frameDurationLegacy;
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp
new file mode 100644
index 0000000..25e768e
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerTracing"
+
+#include "LayerDataSource.h"
+
+#include <log/log.h>
+#include <perfetto/config/android/surfaceflinger_layers_config.pbzero.h>
+
+namespace android {
+
+void LayerDataSource::Initialize(LayerTracing& layerTracing) {
+    mLayerTracing.store(&layerTracing);
+
+    auto args = perfetto::TracingInitArgs{};
+    args.backends = perfetto::kSystemBackend;
+    // We are tracing ~50kb/entry and the default shmem buffer size (256kb) could be overrun.
+    // A shmem buffer overrun typically just stalls layer tracing, however when the stall
+    // lasts for too long perfetto assumes there is a deadlock and aborts surfaceflinger.
+    args.shmem_size_hint_kb = 1024;
+    perfetto::Tracing::Initialize(args);
+
+    perfetto::DataSourceDescriptor descriptor;
+    descriptor.set_name(android::LayerDataSource::kName);
+    LayerDataSource::Register(descriptor);
+}
+
+void LayerDataSource::UnregisterLayerTracing() {
+    mLayerTracing.store(nullptr);
+}
+
+void LayerDataSource::OnSetup(const LayerDataSource::SetupArgs& args) {
+    const auto configRaw = args.config->surfaceflinger_layers_config_raw();
+    const auto config = perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Decoder{configRaw};
+
+    if (config.has_mode() && config.mode() != LayerTracing::Mode::MODE_UNSPECIFIED) {
+        mMode = static_cast<LayerTracing::Mode>(config.mode());
+    } else {
+        mMode = LayerTracing::Mode::MODE_GENERATED;
+        ALOGD("Received config with unspecified 'mode'. Using 'GENERATED' as default");
+    }
+
+    mFlags = 0;
+    for (auto it = config.trace_flags(); it; ++it) {
+        mFlags |= static_cast<uint32_t>(*it);
+    }
+}
+
+void LayerDataSource::OnStart(const LayerDataSource::StartArgs&) {
+    ALOGD("Received OnStart event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+    if (auto* p = mLayerTracing.load()) {
+        p->onStart(mMode, mFlags);
+    }
+}
+
+void LayerDataSource::OnFlush(const LayerDataSource::FlushArgs&) {
+    ALOGD("Received OnFlush event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+    if (auto* p = mLayerTracing.load()) {
+        p->onFlush(mMode, mFlags);
+    }
+}
+
+void LayerDataSource::OnStop(const LayerDataSource::StopArgs&) {
+    ALOGD("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+    if (auto* p = mLayerTracing.load()) {
+        p->onStop(mMode);
+    }
+}
+
+LayerTracing::Mode LayerDataSource::GetMode() const {
+    return mMode;
+}
+
+std::atomic<LayerTracing*> LayerDataSource::mLayerTracing = nullptr;
+
+} // namespace android
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::LayerDataSource);
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.h b/services/surfaceflinger/Tracing/LayerDataSource.h
new file mode 100644
index 0000000..7944092
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LayerDataSource.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "LayerTracing.h"
+
+#include <perfetto/tracing.h>
+
+#include <atomic>
+
+namespace android {
+
+/*
+ * Thread local storage used for fast (lock free) read of data source's properties.
+ *
+ */
+struct LayerDataSourceTlsState {
+    template <typename TraceContext>
+    explicit LayerDataSourceTlsState(const TraceContext& trace_context) {
+        auto dataSource = trace_context.GetDataSourceLocked();
+        mMode = dataSource.valid()
+                ? dataSource->GetMode()
+                : perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Mode::MODE_GENERATED;
+    }
+
+    LayerTracing::Mode mMode;
+};
+
+struct LayerDataSourceTraits : public perfetto::DefaultDataSourceTraits {
+    using TlsStateType = LayerDataSourceTlsState;
+};
+
+/*
+ * Defines the Perfetto custom data source 'android.surfaceflinger.layers'.
+ *
+ * Registers the data source with Perfetto, listens to Perfetto events (setup/start/flush/stop)
+ * and writes trace packets to Perfetto.
+ *
+ */
+class LayerDataSource : public perfetto::DataSource<LayerDataSource, LayerDataSourceTraits> {
+public:
+    static void Initialize(LayerTracing&);
+    static void UnregisterLayerTracing();
+    void OnSetup(const SetupArgs&) override;
+    void OnStart(const StartArgs&) override;
+    void OnFlush(const FlushArgs&) override;
+    void OnStop(const StopArgs&) override;
+    LayerTracing::Mode GetMode() const;
+
+    static constexpr auto* kName = "android.surfaceflinger.layers";
+    static constexpr perfetto::BufferExhaustedPolicy kBufferExhaustedPolicy =
+            perfetto::BufferExhaustedPolicy::kStall;
+    static constexpr bool kRequiresCallbacksUnderLock = false;
+
+private:
+    static std::atomic<LayerTracing*> mLayerTracing;
+    LayerTracing::Mode mMode;
+    std::uint32_t mFlags;
+};
+
+} // namespace android
+
+PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(android::LayerDataSource);
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index ecdeabe..403e105 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -18,130 +18,176 @@
 #define LOG_TAG "LayerTracing"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <filesystem>
-
-#include <SurfaceFlinger.h>
-#include <android-base/stringprintf.h>
-#include <log/log.h>
-#include <utils/SystemClock.h>
-#include <utils/Trace.h>
-
 #include "LayerTracing.h"
-#include "RingBuffer.h"
+
+#include "LayerDataSource.h"
+#include "Tracing/tools/LayerTraceGenerator.h"
+#include "TransactionTracing.h"
+
+#include <log/log.h>
+#include <perfetto/tracing.h>
+#include <utils/Timers.h>
+#include <utils/Trace.h>
 
 namespace android {
 
-LayerTracing::LayerTracing()
-      : mBuffer(std::make_unique<RingBuffer<LayersTraceFileProto, LayersTraceProto>>()) {}
+LayerTracing::LayerTracing() {
+    mTakeLayersSnapshotProto = [](uint32_t) { return perfetto::protos::LayersSnapshotProto{}; };
+    LayerDataSource::Initialize(*this);
+}
 
-LayerTracing::~LayerTracing() = default;
+LayerTracing::LayerTracing(std::ostream& outStream) : LayerTracing() {
+    mOutStream = std::ref(outStream);
+}
 
-bool LayerTracing::enable() {
-    std::scoped_lock lock(mTraceLock);
-    if (mEnabled) {
-        return false;
+LayerTracing::~LayerTracing() {
+    LayerDataSource::UnregisterLayerTracing();
+}
+
+void LayerTracing::setTakeLayersSnapshotProtoFunction(
+        const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>& callback) {
+    mTakeLayersSnapshotProto = callback;
+}
+
+void LayerTracing::setTransactionTracing(TransactionTracing& transactionTracing) {
+    mTransactionTracing = &transactionTracing;
+}
+
+void LayerTracing::onStart(Mode mode, uint32_t flags) {
+    switch (mode) {
+        case Mode::MODE_ACTIVE: {
+            mActiveTracingFlags.store(flags);
+            mIsActiveTracingStarted.store(true);
+            ALOGV("Starting active tracing (waiting for initial snapshot)");
+            // It might take a while before a layers change occurs and a "spontaneous" snapshot is
+            // taken. Let's manually take a snapshot, so that the trace's first entry will contain
+            // the current layers state.
+            addProtoSnapshotToOstream(mTakeLayersSnapshotProto(flags), Mode::MODE_ACTIVE);
+            ALOGD("Started active tracing (traced initial snapshot)");
+            break;
+        }
+        case Mode::MODE_GENERATED: {
+            ALOGD("Started generated tracing (waiting for OnFlush event to generated layers)");
+            break;
+        }
+        case Mode::MODE_DUMP: {
+            auto snapshot = mTakeLayersSnapshotProto(flags);
+            addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP);
+            ALOGD("Started dump tracing (dumped single snapshot)");
+            break;
+        }
+        default: {
+            ALOGE("Started unknown tracing mode (0x%02x)", mode);
+        }
     }
-    mBuffer->setSize(mBufferSizeInBytes);
-    mEnabled = true;
-    return true;
 }
 
-bool LayerTracing::disable(std::string filename, bool writeToFile) {
-    std::scoped_lock lock(mTraceLock);
-    if (!mEnabled) {
-        return false;
+void LayerTracing::onFlush(Mode mode, uint32_t flags) {
+    // In "generated" mode process the buffer of transactions (owned by TransactionTracing),
+    // generate a sequence of layers snapshots and write them to perfetto.
+    if (mode != Mode::MODE_GENERATED) {
+        return;
     }
-    mEnabled = false;
-    if (writeToFile) {
-        LayersTraceFileProto fileProto = createTraceFileProto();
-        mBuffer->writeToFile(fileProto, filename);
+
+    if (!mTransactionTracing) {
+        ALOGD("Skipping layers trace generation (transactions tracing disabled)");
+        return;
     }
-    mBuffer->reset();
-    return true;
+
+    auto transactionTrace = mTransactionTracing->writeToProto();
+    LayerTraceGenerator{}.generate(transactionTrace, flags, *this);
+    ALOGD("Flushed generated tracing");
 }
 
-void LayerTracing::appendToStream(std::ofstream& out) {
-    std::scoped_lock lock(mTraceLock);
-    LayersTraceFileProto fileProto = createTraceFileProto();
-    mBuffer->appendToStream(fileProto, out);
-    mBuffer->reset();
-}
-
-bool LayerTracing::isEnabled() const {
-    std::scoped_lock lock(mTraceLock);
-    return mEnabled;
-}
-
-status_t LayerTracing::writeToFile(std::string filename) {
-    std::scoped_lock lock(mTraceLock);
-    if (!mEnabled) {
-        return STATUS_OK;
+void LayerTracing::onStop(Mode mode) {
+    if (mode == Mode::MODE_ACTIVE) {
+        mIsActiveTracingStarted.store(false);
+        ALOGD("Stopped active tracing");
     }
-    LayersTraceFileProto fileProto = createTraceFileProto();
-    return mBuffer->writeToFile(fileProto, filename);
 }
 
-void LayerTracing::setTraceFlags(uint32_t flags) {
-    std::scoped_lock lock(mTraceLock);
-    mFlags = flags;
+void LayerTracing::addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot,
+                                             Mode mode) {
+    ATRACE_CALL();
+    if (mOutStream) {
+        writeSnapshotToStream(std::move(snapshot));
+    } else {
+        writeSnapshotToPerfetto(snapshot, mode);
+    }
 }
 
-void LayerTracing::setBufferSize(size_t bufferSizeInBytes) {
-    std::scoped_lock lock(mTraceLock);
-    mBufferSizeInBytes = bufferSizeInBytes;
+bool LayerTracing::isActiveTracingStarted() const {
+    return mIsActiveTracingStarted.load();
 }
 
-bool LayerTracing::flagIsSet(uint32_t flags) const {
-    return (mFlags & flags) == flags;
-}
-uint32_t LayerTracing::getFlags() const {
-    return mFlags;
+uint32_t LayerTracing::getActiveTracingFlags() const {
+    return mActiveTracingFlags.load();
 }
 
-LayersTraceFileProto LayerTracing::createTraceFileProto() {
-    LayersTraceFileProto fileProto;
-    fileProto.set_magic_number(uint64_t(LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H) << 32 |
-                               LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
-    auto timeOffsetNs = static_cast<std::uint64_t>(systemTime(SYSTEM_TIME_REALTIME) -
-                                                   systemTime(SYSTEM_TIME_MONOTONIC));
+bool LayerTracing::isActiveTracingFlagSet(Flag flag) const {
+    return (mActiveTracingFlags.load() & flag) != 0;
+}
+
+perfetto::protos::LayersTraceFileProto LayerTracing::createTraceFileProto() {
+    perfetto::protos::LayersTraceFileProto fileProto;
+    fileProto.set_magic_number(
+            static_cast<uint64_t>(perfetto::protos::LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H)
+                    << 32 |
+            perfetto::protos::LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
+    auto timeOffsetNs = static_cast<uint64_t>(systemTime(SYSTEM_TIME_REALTIME) -
+                                              systemTime(SYSTEM_TIME_MONOTONIC));
     fileProto.set_real_to_elapsed_time_offset_nanos(timeOffsetNs);
     return fileProto;
 }
 
-void LayerTracing::dump(std::string& result) const {
-    std::scoped_lock lock(mTraceLock);
-    base::StringAppendF(&result, "Tracing state: %s\n", mEnabled ? "enabled" : "disabled");
-    mBuffer->dump(result);
+void LayerTracing::writeSnapshotToStream(perfetto::protos::LayersSnapshotProto&& snapshot) const {
+    auto fileProto = createTraceFileProto();
+    *fileProto.add_entry() = std::move(snapshot);
+    mOutStream->get() << fileProto.SerializeAsString();
 }
 
-void LayerTracing::notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId,
-                          LayersProto* layers, std::string hwcDump,
-                          google::protobuf::RepeatedPtrField<DisplayProto>* displays) {
-    std::scoped_lock lock(mTraceLock);
-    if (!mEnabled) {
-        return;
+void LayerTracing::writeSnapshotToPerfetto(const perfetto::protos::LayersSnapshotProto& snapshot,
+                                           Mode mode) {
+    const auto snapshotBytes = snapshot.SerializeAsString();
+
+    LayerDataSource::Trace([&](LayerDataSource::TraceContext context) {
+        if (mode != context.GetCustomTlsState()->mMode) {
+            return;
+        }
+        if (!checkAndUpdateLastVsyncIdWrittenToPerfetto(mode, snapshot.vsync_id())) {
+            return;
+        }
+        {
+            auto packet = context.NewTracePacket();
+            packet->set_timestamp(static_cast<uint64_t>(snapshot.elapsed_realtime_nanos()));
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+            auto* snapshotProto = packet->set_surfaceflinger_layers_snapshot();
+            snapshotProto->AppendRawProtoBytes(snapshotBytes.data(), snapshotBytes.size());
+        }
+        {
+            // TODO (b/162206162): remove empty packet when perfetto bug is fixed.
+            //  It is currently needed in order not to lose the last trace entry.
+            context.NewTracePacket();
+        }
+    });
+}
+
+bool LayerTracing::checkAndUpdateLastVsyncIdWrittenToPerfetto(Mode mode, std::int64_t vsyncId) {
+    // In some situations (e.g. two bugreports taken shortly one after the other) the generated
+    // sequence of layers snapshots might overlap. Here we check the snapshot's vsyncid to make
+    // sure that in generated tracing mode a given snapshot is written only once to perfetto.
+    if (mode != Mode::MODE_GENERATED) {
+        return true;
     }
 
-    if (!visibleRegionDirty && !flagIsSet(LayerTracing::TRACE_BUFFERS)) {
-        return;
+    auto lastVsyncId = mLastVsyncIdWrittenToPerfetto.load();
+    while (lastVsyncId < vsyncId) {
+        if (mLastVsyncIdWrittenToPerfetto.compare_exchange_strong(lastVsyncId, vsyncId)) {
+            return true;
+        }
     }
 
-    ATRACE_CALL();
-    LayersTraceProto entry;
-    entry.set_elapsed_realtime_nanos(time);
-    const char* where = visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched";
-    entry.set_where(where);
-    entry.mutable_layers()->Swap(layers);
-
-    if (flagIsSet(LayerTracing::TRACE_HWC)) {
-        entry.set_hwc_blob(hwcDump);
-    }
-    if (!flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
-        entry.set_excludes_composition_state(true);
-    }
-    entry.mutable_displays()->Swap(displays);
-    entry.set_vsync_id(vsyncId);
-    mBuffer->emplace(std::move(entry));
+    return false;
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index 40b0fbe..fe7f06d 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -16,42 +16,77 @@
 
 #pragma once
 
-#include <android-base/thread_annotations.h>
 #include <layerproto/LayerProtoHeader.h>
-#include <utils/Errors.h>
-#include <utils/StrongPointer.h>
-#include <utils/Timers.h>
 
-#include <memory>
-#include <mutex>
-
-using namespace android::surfaceflinger;
+#include <atomic>
+#include <functional>
+#include <optional>
+#include <ostream>
 
 namespace android {
 
-template <typename FileProto, typename EntryProto>
-class RingBuffer;
-
-class SurfaceFlinger;
+class TransactionTracing;
 
 /*
  * LayerTracing records layer states during surface flinging. Manages tracing state and
  * configuration.
+ *
+ * The traced data can then be collected with Perfetto.
+ *
+ * The Perfetto custom data source LayerDataSource is registered with perfetto. The data source
+ * is used to listen to perfetto events (setup, start, stop, flush) and to write trace packets
+ * to perfetto.
+ *
+ * The user can configure/start/stop tracing via /system/bin/perfetto.
+ *
+ * Tracing can operate in the following modes.
+ *
+ * ACTIVE mode:
+ * A layers snapshot is taken and written to perfetto for each vsyncid commit.
+ *
+ * GENERATED mode:
+ * Listens to the perfetto 'flush' event (e.g. when a bugreport is taken).
+ * When a 'flush' event is received, the ring buffer of transactions (hold by TransactionTracing)
+ * is processed by LayerTraceGenerator, a sequence of layers snapshots is generated
+ * and written to perfetto.
+ *
+ * DUMP mode:
+ * When the 'start' event is received a single layers snapshot is taken
+ * and written to perfetto.
+ *
+ *
+ * E.g. start active mode tracing:
+ *
+   adb shell -t perfetto \
+     -c - --txt \
+     -o /data/misc/perfetto-traces/trace \
+   <<EOF
+   unique_session_name: "surfaceflinger_layers_active"
+   buffers: {
+       size_kb: 63488
+       fill_policy: RING_BUFFER
+   }
+   data_sources: {
+       config {
+           name: "android.surfaceflinger.layers"
+           surfaceflinger_layers_config: {
+               mode: MODE_ACTIVE
+               trace_flags: TRACE_FLAG_INPUT
+               trace_flags: TRACE_FLAG_COMPOSITION
+               trace_flags: TRACE_FLAG_HWC
+               trace_flags: TRACE_FLAG_BUFFERS
+               trace_flags: TRACE_FLAG_VIRTUAL_DISPLAYS
+           }
+       }
+   }
+   EOF
+ *
  */
 class LayerTracing {
 public:
-    LayerTracing();
-    ~LayerTracing();
-    bool enable();
-    bool disable(std::string filename = FILE_NAME, bool writeToFile = true);
-    void appendToStream(std::ofstream& out);
-    bool isEnabled() const;
-    status_t writeToFile(std::string filename = FILE_NAME);
-    static LayersTraceFileProto createTraceFileProto();
-    void notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId, LayersProto* layers,
-                std::string hwcDump, google::protobuf::RepeatedPtrField<DisplayProto>* displays);
+    using Mode = perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Mode;
 
-    enum : uint32_t {
+    enum Flag : uint32_t {
         TRACE_INPUT = 1 << 1,
         TRACE_COMPOSITION = 1 << 2,
         TRACE_EXTRA = 1 << 3,
@@ -60,20 +95,39 @@
         TRACE_VIRTUAL_DISPLAYS = 1 << 6,
         TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA,
     };
-    void setTraceFlags(uint32_t flags);
-    bool flagIsSet(uint32_t flags) const;
-    uint32_t getFlags() const;
-    void setBufferSize(size_t bufferSizeInBytes);
-    void dump(std::string&) const;
+
+    LayerTracing();
+    LayerTracing(std::ostream&);
+    ~LayerTracing();
+    void setTakeLayersSnapshotProtoFunction(
+            const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>&);
+    void setTransactionTracing(TransactionTracing&);
+
+    // Start event from perfetto data source
+    void onStart(Mode mode, uint32_t flags);
+    // Flush event from perfetto data source
+    void onFlush(Mode mode, uint32_t flags);
+    // Stop event from perfetto data source
+    void onStop(Mode mode);
+
+    void addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot, Mode mode);
+    bool isActiveTracingStarted() const;
+    uint32_t getActiveTracingFlags() const;
+    bool isActiveTracingFlagSet(Flag flag) const;
+    static perfetto::protos::LayersTraceFileProto createTraceFileProto();
 
 private:
-    static constexpr auto FILE_NAME = "/data/misc/wmtrace/layers_trace.winscope";
-    uint32_t mFlags = TRACE_INPUT;
-    mutable std::mutex mTraceLock;
-    bool mEnabled GUARDED_BY(mTraceLock) = false;
-    std::unique_ptr<RingBuffer<LayersTraceFileProto, LayersTraceProto>> mBuffer
-            GUARDED_BY(mTraceLock);
-    size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = 20 * 1024 * 1024;
+    void writeSnapshotToStream(perfetto::protos::LayersSnapshotProto&& snapshot) const;
+    void writeSnapshotToPerfetto(const perfetto::protos::LayersSnapshotProto& snapshot, Mode mode);
+    bool checkAndUpdateLastVsyncIdWrittenToPerfetto(Mode mode, std::int64_t vsyncId);
+
+    std::function<perfetto::protos::LayersSnapshotProto(uint32_t)> mTakeLayersSnapshotProto;
+    TransactionTracing* mTransactionTracing;
+
+    std::atomic<bool> mIsActiveTracingStarted{false};
+    std::atomic<uint32_t> mActiveTracingFlags{0};
+    std::atomic<std::int64_t> mLastVsyncIdWrittenToPerfetto{-1};
+    std::optional<std::reference_wrapper<std::ostream>> mOutStream;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionDataSource.cpp b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
new file mode 100644
index 0000000..6c9ed30
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TransactionDataSource.h"
+#include "TransactionTracing.h"
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTracing"
+
+#include <log/log.h>
+
+namespace android {
+
+void TransactionDataSource::Initialize(TransactionTracing& transactionTracing) {
+    mTransactionTracing.store(&transactionTracing);
+
+    auto args = perfetto::TracingInitArgs{};
+    args.backends = perfetto::kSystemBackend;
+    perfetto::Tracing::Initialize(args);
+
+    perfetto::DataSourceDescriptor descriptor;
+    descriptor.set_name(kName);
+    TransactionDataSource::Register(descriptor);
+}
+
+void TransactionDataSource::UnregisterTransactionTracing() {
+    mTransactionTracing.store(nullptr);
+}
+
+void TransactionDataSource::OnSetup(const TransactionDataSource::SetupArgs& args) {
+    const auto configRaw = args.config->surfaceflinger_transactions_config_raw();
+    const auto config =
+            perfetto::protos::pbzero::SurfaceFlingerTransactionsConfig::Decoder{configRaw};
+
+    if (config.has_mode() && config.mode() != TransactionTracing::Mode::MODE_UNSPECIFIED) {
+        mMode = static_cast<TransactionTracing::Mode>(config.mode());
+    } else {
+        mMode = TransactionTracing::Mode::MODE_CONTINUOUS;
+        ALOGD("Received config with unspecified 'mode'. Using 'CONTINUOUS' as default");
+    }
+}
+
+void TransactionDataSource::OnStart(const StartArgs&) {
+    ALOGD("Received OnStart event");
+    if (auto* p = mTransactionTracing.load()) {
+        p->onStart(mMode);
+    }
+}
+
+void TransactionDataSource::OnFlush(const FlushArgs&) {
+    ALOGD("Received OnFlush event");
+    if (auto* p = mTransactionTracing.load()) {
+        p->onFlush(mMode);
+    }
+}
+
+void TransactionDataSource::OnStop(const StopArgs&) {
+    ALOGD("Received OnStop event");
+}
+
+TransactionTracing::Mode TransactionDataSource::GetMode() const {
+    return mMode;
+}
+
+std::atomic<TransactionTracing*> TransactionDataSource::mTransactionTracing = nullptr;
+
+} // namespace android
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::TransactionDataSource);
diff --git a/services/surfaceflinger/Tracing/TransactionDataSource.h b/services/surfaceflinger/Tracing/TransactionDataSource.h
new file mode 100644
index 0000000..be8373b
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionDataSource.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "TransactionTracing.h"
+
+#include <perfetto/tracing.h>
+
+namespace android {
+
+/*
+ * Thread local storage used for fast (lock free) read of data source's properties.
+ *
+ */
+struct TransactionDataSourceTlsState {
+    template <typename TraceContext>
+    explicit TransactionDataSourceTlsState(const TraceContext& trace_context) {
+        auto dataSource = trace_context.GetDataSourceLocked();
+        mMode = dataSource.valid() ? dataSource->GetMode()
+                                   : TransactionTracing::Mode::MODE_CONTINUOUS;
+    }
+
+    TransactionTracing::Mode mMode;
+};
+
+struct TransactionDataSourceTraits : public perfetto::DefaultDataSourceTraits {
+    using TlsStateType = TransactionDataSourceTlsState;
+};
+
+/*
+ * Defines the Perfetto custom data source 'android.surfaceflinger.transactions'.
+ *
+ * Registers the data source with Perfetto, listens to Perfetto events (setup/start/flush/stop)
+ * and writes trace packets to Perfetto.
+ *
+ */
+class TransactionDataSource
+      : public perfetto::DataSource<TransactionDataSource, TransactionDataSourceTraits> {
+public:
+    static void Initialize(TransactionTracing&);
+    static void UnregisterTransactionTracing();
+    void OnSetup(const SetupArgs&) override;
+    void OnStart(const StartArgs&) override;
+    void OnFlush(const FlushArgs&) override;
+    void OnStop(const StopArgs&) override;
+    TransactionTracing::Mode GetMode() const;
+
+    static constexpr auto* kName = "android.surfaceflinger.transactions";
+    static constexpr perfetto::BufferExhaustedPolicy kBufferExhaustedPolicy =
+            perfetto::BufferExhaustedPolicy::kStall;
+    static constexpr bool kRequiresCallbacksUnderLock = false;
+
+private:
+    static std::atomic<TransactionTracing*> mTransactionTracing;
+    TransactionTracing::Mode mMode;
+};
+
+} // namespace android
+
+PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(android::TransactionDataSource);
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index b1e3d63..2dc89b5 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -51,8 +51,8 @@
     ~FakeExternalTexture() = default;
 };
 
-proto::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
-    proto::TransactionState proto;
+perfetto::protos::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
+    perfetto::protos::TransactionState proto;
     proto.set_pid(t.originPid);
     proto.set_uid(t.originUid);
     proto.set_vsync_id(t.frameTimelineInfo.vsyncId);
@@ -79,21 +79,21 @@
     return proto;
 }
 
-proto::TransactionState TransactionProtoParser::toProto(
+perfetto::protos::TransactionState TransactionProtoParser::toProto(
         const std::map<uint32_t /* layerId */, TracingLayerState>& states) {
-    proto::TransactionState proto;
+    perfetto::protos::TransactionState proto;
     proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(states.size()));
     for (auto& [layerId, state] : states) {
-        proto::LayerState layerProto = toProto(state);
+        perfetto::protos::LayerState layerProto = toProto(state);
         layerProto.set_has_sideband_stream(state.hasSidebandStream);
         proto.mutable_layer_changes()->Add(std::move(layerProto));
     }
     return proto;
 }
 
-proto::LayerState TransactionProtoParser::toProto(
+perfetto::protos::LayerState TransactionProtoParser::toProto(
         const ResolvedComposerState& resolvedComposerState) {
-    proto::LayerState proto;
+    perfetto::protos::LayerState proto;
     auto& layer = resolvedComposerState.state;
     proto.set_layer_id(resolvedComposerState.layerId);
     proto.set_what(layer.what);
@@ -114,7 +114,7 @@
         proto.set_mask(layer.mask);
     }
     if (layer.what & layer_state_t::eMatrixChanged) {
-        proto::LayerState_Matrix22* matrixProto = proto.mutable_matrix();
+        perfetto::protos::LayerState_Matrix22* matrixProto = proto.mutable_matrix();
         matrixProto->set_dsdx(layer.matrix.dsdx);
         matrixProto->set_dsdy(layer.matrix.dsdy);
         matrixProto->set_dtdx(layer.matrix.dtdx);
@@ -132,7 +132,7 @@
     }
 
     if (layer.what & layer_state_t::eColorChanged) {
-        proto::LayerState_Color3* colorProto = proto.mutable_color();
+        perfetto::protos::LayerState_Color3* colorProto = proto.mutable_color();
         colorProto->set_r(layer.color.r);
         colorProto->set_g(layer.color.g);
         colorProto->set_b(layer.color.b);
@@ -150,13 +150,14 @@
         LayerProtoHelper::writeToProto(layer.crop, proto.mutable_crop());
     }
     if (layer.what & layer_state_t::eBufferChanged) {
-        proto::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
+        perfetto::protos::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
         if (resolvedComposerState.externalTexture) {
             bufferProto->set_buffer_id(resolvedComposerState.externalTexture->getId());
             bufferProto->set_width(resolvedComposerState.externalTexture->getWidth());
             bufferProto->set_height(resolvedComposerState.externalTexture->getHeight());
-            bufferProto->set_pixel_format(static_cast<proto::LayerState_BufferData_PixelFormat>(
-                    resolvedComposerState.externalTexture->getPixelFormat()));
+            bufferProto->set_pixel_format(
+                    static_cast<perfetto::protos::LayerState_BufferData_PixelFormat>(
+                            resolvedComposerState.externalTexture->getPixelFormat()));
             bufferProto->set_usage(resolvedComposerState.externalTexture->getUsage());
         }
         bufferProto->set_frame_number(layer.bufferData->frameNumber);
@@ -191,7 +192,8 @@
     if (layer.what & layer_state_t::eInputInfoChanged) {
         if (layer.windowInfoHandle) {
             const gui::WindowInfo* inputInfo = layer.windowInfoHandle->getInfo();
-            proto::LayerState_WindowInfo* windowInfoProto = proto.mutable_window_info_handle();
+            perfetto::protos::LayerState_WindowInfo* windowInfoProto =
+                    proto.mutable_window_info_handle();
             windowInfoProto->set_layout_params_flags(inputInfo->layoutParamsFlags.get());
             windowInfoProto->set_layout_params_type(
                     static_cast<int32_t>(inputInfo->layoutParamsType));
@@ -204,7 +206,7 @@
             windowInfoProto->set_has_wallpaper(inputInfo->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER));
             windowInfoProto->set_global_scale_factor(inputInfo->globalScaleFactor);
-            proto::Transform* transformProto = windowInfoProto->mutable_transform();
+            perfetto::protos::Transform* transformProto = windowInfoProto->mutable_transform();
             transformProto->set_dsdx(inputInfo->transform.dsdx());
             transformProto->set_dtdx(inputInfo->transform.dtdx());
             transformProto->set_dtdy(inputInfo->transform.dtdy());
@@ -219,7 +221,7 @@
     if (layer.what & layer_state_t::eBackgroundColorChanged) {
         proto.set_bg_color_alpha(layer.bgColor.a);
         proto.set_bg_color_dataspace(static_cast<int32_t>(layer.bgColorDataspace));
-        proto::LayerState_Color3* colorProto = proto.mutable_color();
+        perfetto::protos::LayerState_Color3* colorProto = proto.mutable_color();
         colorProto->set_r(layer.bgColor.r);
         colorProto->set_g(layer.bgColor.g);
         colorProto->set_b(layer.bgColor.b);
@@ -255,13 +257,13 @@
     }
     if (layer.what & layer_state_t::eDropInputModeChanged) {
         proto.set_drop_input_mode(
-                static_cast<proto::LayerState_DropInputMode>(layer.dropInputMode));
+                static_cast<perfetto::protos::LayerState_DropInputMode>(layer.dropInputMode));
     }
     return proto;
 }
 
-proto::DisplayState TransactionProtoParser::toProto(const DisplayState& display) {
-    proto::DisplayState proto;
+perfetto::protos::DisplayState TransactionProtoParser::toProto(const DisplayState& display) {
+    perfetto::protos::DisplayState proto;
     proto.set_what(display.what);
     proto.set_id(mMapper->getDisplayId(display.token));
 
@@ -285,8 +287,8 @@
     return proto;
 }
 
-proto::LayerCreationArgs TransactionProtoParser::toProto(const LayerCreationArgs& args) {
-    proto::LayerCreationArgs proto;
+perfetto::protos::LayerCreationArgs TransactionProtoParser::toProto(const LayerCreationArgs& args) {
+    perfetto::protos::LayerCreationArgs proto;
     proto.set_layer_id(args.sequence);
     proto.set_name(args.name);
     proto.set_flags(args.flags);
@@ -297,7 +299,8 @@
     return proto;
 }
 
-TransactionState TransactionProtoParser::fromProto(const proto::TransactionState& proto) {
+TransactionState TransactionProtoParser::fromProto(
+        const perfetto::protos::TransactionState& proto) {
     TransactionState t;
     t.originPid = proto.pid();
     t.originUid = proto.uid();
@@ -323,7 +326,7 @@
     return t;
 }
 
-void TransactionProtoParser::fromProto(const proto::LayerCreationArgs& proto,
+void TransactionProtoParser::fromProto(const perfetto::protos::LayerCreationArgs& proto,
                                        LayerCreationArgs& outArgs) {
     outArgs.sequence = proto.layer_id();
 
@@ -335,7 +338,7 @@
     outArgs.layerStackToMirror.id = proto.layer_stack_to_mirror();
 }
 
-void TransactionProtoParser::mergeFromProto(const proto::LayerState& proto,
+void TransactionProtoParser::mergeFromProto(const perfetto::protos::LayerState& proto,
                                             TracingLayerState& outState) {
     ResolvedComposerState resolvedComposerState;
     fromProto(proto, resolvedComposerState);
@@ -360,7 +363,7 @@
     }
 }
 
-void TransactionProtoParser::fromProto(const proto::LayerState& proto,
+void TransactionProtoParser::fromProto(const perfetto::protos::LayerState& proto,
                                        ResolvedComposerState& resolvedComposerState) {
     auto& layer = resolvedComposerState.state;
     resolvedComposerState.layerId = proto.layer_id();
@@ -381,7 +384,7 @@
         layer.mask = proto.mask();
     }
     if (proto.what() & layer_state_t::eMatrixChanged) {
-        const proto::LayerState_Matrix22& matrixProto = proto.matrix();
+        const perfetto::protos::LayerState_Matrix22& matrixProto = proto.matrix();
         layer.matrix.dsdx = matrixProto.dsdx();
         layer.matrix.dsdy = matrixProto.dsdy();
         layer.matrix.dtdx = matrixProto.dtdx();
@@ -399,7 +402,7 @@
     }
 
     if (proto.what() & layer_state_t::eColorChanged) {
-        const proto::LayerState_Color3& colorProto = proto.color();
+        const perfetto::protos::LayerState_Color3& colorProto = proto.color();
         layer.color.r = colorProto.r();
         layer.color.g = colorProto.g();
         layer.color.b = colorProto.b();
@@ -417,7 +420,7 @@
         LayerProtoHelper::readFromProto(proto.crop(), layer.crop);
     }
     if (proto.what() & layer_state_t::eBufferChanged) {
-        const proto::LayerState_BufferData& bufferProto = proto.buffer_data();
+        const perfetto::protos::LayerState_BufferData& bufferProto = proto.buffer_data();
         layer.bufferData =
                 std::make_shared<fake::BufferData>(bufferProto.buffer_id(), bufferProto.width(),
                                                    bufferProto.height(), bufferProto.pixel_format(),
@@ -460,7 +463,7 @@
 
     if ((proto.what() & layer_state_t::eInputInfoChanged) && proto.has_window_info_handle()) {
         gui::WindowInfo inputInfo;
-        const proto::LayerState_WindowInfo& windowInfoProto = proto.window_info_handle();
+        const perfetto::protos::LayerState_WindowInfo& windowInfoProto = proto.window_info_handle();
 
         inputInfo.layoutParamsFlags =
                 static_cast<gui::WindowInfo::Flag>(windowInfoProto.layout_params_flags());
@@ -472,7 +475,7 @@
                 ftl::Flags<gui::WindowInfo::InputConfig>(windowInfoProto.input_config());
         inputInfo.surfaceInset = windowInfoProto.surface_inset();
         inputInfo.globalScaleFactor = windowInfoProto.global_scale_factor();
-        const proto::Transform& transformProto = windowInfoProto.transform();
+        const perfetto::protos::Transform& transformProto = windowInfoProto.transform();
         inputInfo.transform.set(transformProto.dsdx(), transformProto.dtdx(), transformProto.dtdy(),
                                 transformProto.dsdy());
         inputInfo.transform.set(transformProto.tx(), transformProto.ty());
@@ -485,7 +488,7 @@
     if (proto.what() & layer_state_t::eBackgroundColorChanged) {
         layer.bgColor.a = proto.bg_color_alpha();
         layer.bgColorDataspace = static_cast<ui::Dataspace>(proto.bg_color_dataspace());
-        const proto::LayerState_Color3& colorProto = proto.color();
+        const perfetto::protos::LayerState_Color3& colorProto = proto.color();
         layer.bgColor.r = colorProto.r();
         layer.bgColor.g = colorProto.g();
         layer.bgColor.b = colorProto.b();
@@ -525,7 +528,7 @@
     }
 }
 
-DisplayState TransactionProtoParser::fromProto(const proto::DisplayState& proto) {
+DisplayState TransactionProtoParser::fromProto(const perfetto::protos::DisplayState& proto) {
     DisplayState display;
     display.what = proto.what();
     display.token = mMapper->getDisplayHandle(proto.id());
@@ -550,7 +553,7 @@
     return display;
 }
 
-void asProto(proto::Transform* proto, const ui::Transform& transform) {
+void asProto(perfetto::protos::Transform* proto, const ui::Transform& transform) {
     proto->set_dsdx(transform.dsdx());
     proto->set_dtdx(transform.dtdx());
     proto->set_dtdy(transform.dtdy());
@@ -559,9 +562,9 @@
     proto->set_ty(transform.ty());
 }
 
-proto::DisplayInfo TransactionProtoParser::toProto(const frontend::DisplayInfo& displayInfo,
-                                                   uint32_t layerStack) {
-    proto::DisplayInfo proto;
+perfetto::protos::DisplayInfo TransactionProtoParser::toProto(
+        const frontend::DisplayInfo& displayInfo, uint32_t layerStack) {
+    perfetto::protos::DisplayInfo proto;
     proto.set_layer_stack(layerStack);
     proto.set_display_id(displayInfo.info.displayId);
     proto.set_logical_width(displayInfo.info.logicalWidth);
@@ -577,12 +580,13 @@
     return proto;
 }
 
-void fromProto2(ui::Transform& outTransform, const proto::Transform& proto) {
+void fromProto2(ui::Transform& outTransform, const perfetto::protos::Transform& proto) {
     outTransform.set(proto.dsdx(), proto.dtdx(), proto.dtdy(), proto.dsdy());
     outTransform.set(proto.tx(), proto.ty());
 }
 
-frontend::DisplayInfo TransactionProtoParser::fromProto(const proto::DisplayInfo& proto) {
+frontend::DisplayInfo TransactionProtoParser::fromProto(
+        const perfetto::protos::DisplayInfo& proto) {
     frontend::DisplayInfo displayInfo;
     displayInfo.info.displayId = proto.display_id();
     displayInfo.info.logicalWidth = proto.logical_width();
@@ -599,10 +603,10 @@
 }
 
 void TransactionProtoParser::fromProto(
-        const google::protobuf::RepeatedPtrField<proto::DisplayInfo>& proto,
+        const google::protobuf::RepeatedPtrField<perfetto::protos::DisplayInfo>& proto,
         frontend::DisplayInfos& outDisplayInfos) {
     outDisplayInfos.clear();
-    for (const proto::DisplayInfo& displayInfo : proto) {
+    for (const perfetto::protos::DisplayInfo& displayInfo : proto) {
         outDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(displayInfo.layer_stack()),
                                            fromProto(displayInfo));
     }
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h
index 457c3be..b3ab71c 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.h
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h
@@ -44,25 +44,25 @@
     TransactionProtoParser(std::unique_ptr<FlingerDataMapper> provider)
           : mMapper(std::move(provider)) {}
 
-    proto::TransactionState toProto(const TransactionState&);
-    proto::TransactionState toProto(const std::map<uint32_t /* layerId */, TracingLayerState>&);
-    proto::LayerCreationArgs toProto(const LayerCreationArgs& args);
-    proto::LayerState toProto(const ResolvedComposerState&);
-    static proto::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack);
+    perfetto::protos::TransactionState toProto(const TransactionState&);
+    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 proto::TransactionState&);
-    void mergeFromProto(const proto::LayerState&, TracingLayerState& outState);
-    void fromProto(const proto::LayerCreationArgs&, LayerCreationArgs& outArgs);
+    TransactionState 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;
-    static frontend::DisplayInfo fromProto(const proto::DisplayInfo&);
-    static void fromProto(const google::protobuf::RepeatedPtrField<proto::DisplayInfo>&,
+    static frontend::DisplayInfo fromProto(const perfetto::protos::DisplayInfo&);
+    static void fromProto(const google::protobuf::RepeatedPtrField<perfetto::protos::DisplayInfo>&,
                           frontend::DisplayInfos& outDisplayInfos);
 
 private:
-    proto::DisplayState toProto(const DisplayState&);
-    void fromProto(const proto::LayerState&, ResolvedComposerState& out);
-    DisplayState fromProto(const proto::DisplayState&);
-
+    perfetto::protos::DisplayState toProto(const DisplayState&);
+    void fromProto(const perfetto::protos::LayerState&, ResolvedComposerState& out);
+    DisplayState fromProto(const perfetto::protos::DisplayState&);
 };
 
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
similarity index 84%
rename from services/surfaceflinger/Tracing/RingBuffer.h
rename to services/surfaceflinger/Tracing/TransactionRingBuffer.h
index b41c65b..7d1d3fd 100644
--- a/services/surfaceflinger/Tracing/RingBuffer.h
+++ b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
@@ -32,7 +32,7 @@
 class SurfaceFlinger;
 
 template <typename FileProto, typename EntryProto>
-class RingBuffer {
+class TransactionRingBuffer {
 public:
     size_t size() const { return mSizeInBytes; }
     size_t used() const { return mUsedInBytes; }
@@ -47,7 +47,7 @@
         mUsedInBytes = 0U;
     }
 
-    void writeToProto(FileProto& fileProto) {
+    void writeToProto(FileProto& fileProto) const {
         fileProto.mutable_entry()->Reserve(static_cast<int>(mStorage.size()) +
                                            fileProto.entry().size());
         for (const std::string& entry : mStorage) {
@@ -56,24 +56,6 @@
         }
     }
 
-    status_t writeToFile(FileProto& fileProto, std::string filename) {
-        ATRACE_CALL();
-        writeToProto(fileProto);
-        std::string output;
-        if (!fileProto.SerializeToString(&output)) {
-            ALOGE("Could not serialize proto.");
-            return UNKNOWN_ERROR;
-        }
-
-        // -rw-r--r--
-        const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
-        if (!android::base::WriteStringToFile(output, filename, mode, getuid(), getgid(), true)) {
-            ALOGE("Could not save the proto file %s", filename.c_str());
-            return PERMISSION_DENIED;
-        }
-        return NO_ERROR;
-    }
-
     status_t appendToStream(FileProto& fileProto, std::ofstream& out) {
         ATRACE_CALL();
         writeToProto(fileProto);
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 7e330b9..9d6d87e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -25,6 +25,7 @@
 
 #include "Client.h"
 #include "FrontEnd/LayerCreationArgs.h"
+#include "TransactionDataSource.h"
 #include "TransactionTracing.h"
 
 namespace android {
@@ -34,15 +35,20 @@
       : mProtoParser(std::make_unique<TransactionProtoParser::FlingerDataMapper>()) {
     std::scoped_lock lock(mTraceLock);
 
-    mBuffer.setSize(mBufferSizeInBytes);
+    mBuffer.setSize(CONTINUOUS_TRACING_BUFFER_SIZE);
+
     mStartingTimestamp = systemTime();
+
     {
         std::scoped_lock lock(mMainThreadLock);
         mThread = std::thread(&TransactionTracing::loop, this);
     }
+
+    TransactionDataSource::Initialize(*this);
 }
 
 TransactionTracing::~TransactionTracing() {
+    TransactionDataSource::UnregisterTransactionTracing();
     std::thread thread;
     {
         std::scoped_lock lock(mMainThreadLock);
@@ -53,29 +59,103 @@
     if (thread.joinable()) {
         thread.join();
     }
+}
 
-    writeToFile();
+void TransactionTracing::onStart(TransactionTracing::Mode mode) {
+    // In "active" mode write the ring buffer (starting state + following sequence of transactions)
+    // to perfetto when tracing starts (only once).
+    if (mode != Mode::MODE_ACTIVE) {
+        return;
+    }
+
+    writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_ACTIVE);
+
+    ALOGD("Started active mode tracing (wrote initial transactions ring buffer to perfetto)");
+}
+
+void TransactionTracing::onFlush(TransactionTracing::Mode mode) {
+    // In "continuous" mode write the ring buffer (starting state + following sequence of
+    // transactions) to perfetto when a "flush" event is received (bugreport is taken or tracing is
+    // stopped).
+    if (mode != Mode::MODE_CONTINUOUS) {
+        return;
+    }
+
+    writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_CONTINUOUS);
+
+    ALOGD("Flushed continuous mode tracing (wrote transactions ring buffer to perfetto");
+}
+
+void TransactionTracing::writeRingBufferToPerfetto(TransactionTracing::Mode mode) {
+    // Write the ring buffer (starting state + following sequence of transactions) to perfetto
+    // tracing sessions with the specified mode.
+    const auto fileProto = writeToProto();
+
+    TransactionDataSource::Trace([&](TransactionDataSource::TraceContext context) {
+        // Write packets only to tracing sessions with specified mode
+        if (context.GetCustomTlsState()->mMode != mode) {
+            return;
+        }
+        for (const auto& entryProto : fileProto.entry()) {
+            const auto entryBytes = entryProto.SerializeAsString();
+
+            auto packet = context.NewTracePacket();
+            packet->set_timestamp(static_cast<uint64_t>(entryProto.elapsed_realtime_nanos()));
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+
+            auto* transactionsProto = packet->set_surfaceflinger_transactions();
+            transactionsProto->AppendRawProtoBytes(entryBytes.data(), entryBytes.size());
+        }
+        {
+            // TODO (b/162206162): remove empty packet when perfetto bug is fixed.
+            //  It is currently needed in order not to lose the last trace entry.
+            context.NewTracePacket();
+        }
+    });
 }
 
 status_t TransactionTracing::writeToFile(const std::string& filename) {
-    std::scoped_lock lock(mTraceLock);
-    proto::TransactionTraceFile fileProto = createTraceFileProto();
-    addStartingStateToProtoLocked(fileProto);
-    return mBuffer.writeToFile(fileProto, filename);
+    auto fileProto = writeToProto();
+
+    std::string output;
+    if (!fileProto.SerializeToString(&output)) {
+        ALOGE("Could not serialize proto.");
+        return UNKNOWN_ERROR;
+    }
+
+    // -rw-r--r--
+    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    if (!android::base::WriteStringToFile(output, filename, mode, getuid(), getgid(), true)) {
+        ALOGE("Could not save the proto file %s", filename.c_str());
+        return PERMISSION_DENIED;
+    }
+
+    return NO_ERROR;
+}
+
+perfetto::protos::TransactionTraceFile TransactionTracing::writeToProto() {
+    std::scoped_lock<std::mutex> lock(mTraceLock);
+    perfetto::protos::TransactionTraceFile fileProto = createTraceFileProto();
+    const auto startingStateProto = createStartingStateProtoLocked();
+    if (startingStateProto) {
+        *fileProto.add_entry() = std::move(*startingStateProto);
+    }
+    mBuffer.writeToProto(fileProto);
+    return fileProto;
 }
 
 void TransactionTracing::setBufferSize(size_t bufferSizeInBytes) {
     std::scoped_lock lock(mTraceLock);
-    mBufferSizeInBytes = bufferSizeInBytes;
-    mBuffer.setSize(mBufferSizeInBytes);
+    mBuffer.setSize(bufferSizeInBytes);
 }
 
-proto::TransactionTraceFile TransactionTracing::createTraceFileProto() const {
-    proto::TransactionTraceFile proto;
-    proto.set_magic_number(uint64_t(proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_H) << 32 |
-                           proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_L);
-    auto timeOffsetNs = static_cast<std::uint64_t>(systemTime(SYSTEM_TIME_REALTIME) -
-                                                   systemTime(SYSTEM_TIME_MONOTONIC));
+perfetto::protos::TransactionTraceFile TransactionTracing::createTraceFileProto() const {
+    perfetto::protos::TransactionTraceFile proto;
+    proto.set_magic_number(
+            uint64_t(perfetto::protos::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_H) << 32 |
+            perfetto::protos::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_L);
+    auto timeOffsetNs = static_cast<uint64_t>(systemTime(SYSTEM_TIME_REALTIME) -
+                                              systemTime(SYSTEM_TIME_MONOTONIC));
     proto.set_real_to_elapsed_time_offset_nanos(timeOffsetNs);
     proto.set_version(TRACING_VERSION);
     return proto;
@@ -89,7 +169,8 @@
 }
 
 void TransactionTracing::addQueuedTransaction(const TransactionState& transaction) {
-    proto::TransactionState* state = new proto::TransactionState(mProtoParser.toProto(transaction));
+    perfetto::protos::TransactionState* state =
+            new perfetto::protos::TransactionState(mProtoParser.toProto(transaction));
     mTransactionQueue.push(state);
 }
 
@@ -111,7 +192,7 @@
     update.createdLayers = std::move(newUpdate.layerCreationArgs);
     newUpdate.layerCreationArgs.clear();
     update.destroyedLayerHandles.reserve(newUpdate.destroyedHandles.size());
-    for (uint32_t handle : newUpdate.destroyedHandles) {
+    for (auto& [handle, _] : newUpdate.destroyedHandles) {
         update.destroyedLayerHandles.push_back(handle);
     }
     mPendingUpdates.emplace_back(update);
@@ -152,7 +233,7 @@
     ATRACE_CALL();
     std::scoped_lock lock(mTraceLock);
     std::vector<std::string> removedEntries;
-    proto::TransactionTraceEntry entryProto;
+    perfetto::protos::TransactionTraceEntry entryProto;
 
     while (auto incomingTransaction = mTransactionQueue.pop()) {
         auto transaction = *incomingTransaction;
@@ -204,14 +285,37 @@
 
         std::string serializedProto;
         entryProto.SerializeToString(&serializedProto);
-        entryProto.Clear();
+
+        TransactionDataSource::Trace([&](TransactionDataSource::TraceContext context) {
+            // In "active" mode write each committed transaction to perfetto.
+            // Note: the starting state is written (once) when the perfetto "start" event is
+            // received.
+            if (context.GetCustomTlsState()->mMode != Mode::MODE_ACTIVE) {
+                return;
+            }
+            {
+                auto packet = context.NewTracePacket();
+                packet->set_timestamp(static_cast<uint64_t>(entryProto.elapsed_realtime_nanos()));
+                packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+                auto* transactions = packet->set_surfaceflinger_transactions();
+                transactions->AppendRawProtoBytes(serializedProto.data(), serializedProto.size());
+            }
+            {
+                // TODO (b/162206162): remove empty packet when perfetto bug is fixed.
+                //  It is currently needed in order not to lose the last trace entry.
+                context.NewTracePacket();
+            }
+        });
+
         std::vector<std::string> entries = mBuffer.emplace(std::move(serializedProto));
         removedEntries.reserve(removedEntries.size() + entries.size());
         removedEntries.insert(removedEntries.end(), std::make_move_iterator(entries.begin()),
                               std::make_move_iterator(entries.end()));
+
+        entryProto.Clear();
     }
 
-    proto::TransactionTraceEntry removedEntryProto;
+    perfetto::protos::TransactionTraceEntry removedEntryProto;
     for (const std::string& removedEntry : removedEntries) {
         removedEntryProto.ParseFromString(removedEntry);
         updateStartingStateLocked(removedEntryProto);
@@ -236,7 +340,7 @@
     base::ScopedLockAssertion assumeLocked(mTraceLock);
     mTransactionsAddedToBufferCv.wait_for(lock, std::chrono::milliseconds(100),
                                           [&]() REQUIRES(mTraceLock) {
-                                              proto::TransactionTraceEntry entry;
+                                              perfetto::protos::TransactionTraceEntry entry;
                                               if (mBuffer.used() > 0) {
                                                   entry.ParseFromString(mBuffer.back());
                                               }
@@ -268,19 +372,19 @@
 }
 
 void TransactionTracing::updateStartingStateLocked(
-        const proto::TransactionTraceEntry& removedEntry) {
+        const perfetto::protos::TransactionTraceEntry& removedEntry) {
     mStartingTimestamp = removedEntry.elapsed_realtime_nanos();
     // Keep track of layer starting state so we can reconstruct the layer state as we purge
     // transactions from the buffer.
-    for (const proto::LayerCreationArgs& addedLayer : removedEntry.added_layers()) {
+    for (const perfetto::protos::LayerCreationArgs& addedLayer : removedEntry.added_layers()) {
         TracingLayerState& startingState = mStartingStates[addedLayer.layer_id()];
         startingState.layerId = addedLayer.layer_id();
         mProtoParser.fromProto(addedLayer, startingState.args);
     }
 
     // Merge layer states to starting transaction state.
-    for (const proto::TransactionState& transaction : removedEntry.transactions()) {
-        for (const proto::LayerState& layerState : transaction.layer_changes()) {
+    for (const perfetto::protos::TransactionState& transaction : removedEntry.transactions()) {
+        for (const perfetto::protos::LayerState& layerState : transaction.layer_changes()) {
             auto it = mStartingStates.find(layerState.layer_id());
             if (it == mStartingStates.end()) {
                 // TODO(b/238781169) make this log fatal when we switch over to using new fe
@@ -307,43 +411,38 @@
     }
 }
 
-void TransactionTracing::addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) {
-    if (mStartingStates.size() == 0) {
-        return;
+std::optional<perfetto::protos::TransactionTraceEntry>
+TransactionTracing::createStartingStateProtoLocked() {
+    if (mStartingStates.empty()) {
+        return std::nullopt;
     }
 
-    proto::TransactionTraceEntry* entryProto = proto.add_entry();
-    entryProto->set_elapsed_realtime_nanos(mStartingTimestamp);
-    entryProto->set_vsync_id(0);
+    perfetto::protos::TransactionTraceEntry entryProto;
+    entryProto.set_elapsed_realtime_nanos(mStartingTimestamp);
+    entryProto.set_vsync_id(0);
 
-    entryProto->mutable_added_layers()->Reserve(static_cast<int32_t>(mStartingStates.size()));
+    entryProto.mutable_added_layers()->Reserve(static_cast<int32_t>(mStartingStates.size()));
     for (auto& [layerId, state] : mStartingStates) {
-        entryProto->mutable_added_layers()->Add(mProtoParser.toProto(state.args));
+        entryProto.mutable_added_layers()->Add(mProtoParser.toProto(state.args));
     }
 
-    proto::TransactionState transactionProto = mProtoParser.toProto(mStartingStates);
+    perfetto::protos::TransactionState transactionProto = mProtoParser.toProto(mStartingStates);
     transactionProto.set_vsync_id(0);
     transactionProto.set_post_time(mStartingTimestamp);
-    entryProto->mutable_transactions()->Add(std::move(transactionProto));
+    entryProto.mutable_transactions()->Add(std::move(transactionProto));
 
-    entryProto->mutable_destroyed_layer_handles()->Reserve(
+    entryProto.mutable_destroyed_layer_handles()->Reserve(
             static_cast<int32_t>(mRemovedLayerHandlesAtStart.size()));
     for (const uint32_t destroyedLayerHandleId : mRemovedLayerHandlesAtStart) {
-        entryProto->mutable_destroyed_layer_handles()->Add(destroyedLayerHandleId);
+        entryProto.mutable_destroyed_layer_handles()->Add(destroyedLayerHandleId);
     }
 
-    entryProto->mutable_displays()->Reserve(static_cast<int32_t>(mStartingDisplayInfos.size()));
+    entryProto.mutable_displays()->Reserve(static_cast<int32_t>(mStartingDisplayInfos.size()));
     for (auto& [layerStack, displayInfo] : mStartingDisplayInfos) {
-        entryProto->mutable_displays()->Add(mProtoParser.toProto(displayInfo, layerStack.id));
+        entryProto.mutable_displays()->Add(mProtoParser.toProto(displayInfo, layerStack.id));
     }
-}
 
-proto::TransactionTraceFile TransactionTracing::writeToProto() {
-    std::scoped_lock<std::mutex> lock(mTraceLock);
-    proto::TransactionTraceFile proto = createTraceFileProto();
-    addStartingStateToProtoLocked(proto);
-    mBuffer.writeToProto(proto);
-    return proto;
+    return entryProto;
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index a59dc6e..ddbf3e4 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -22,16 +22,16 @@
 #include <utils/Singleton.h>
 #include <utils/Timers.h>
 
-#include <memory>
 #include <mutex>
+#include <optional>
 #include <thread>
 
 #include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/Update.h"
 #include "LocklessStack.h"
-#include "RingBuffer.h"
 #include "TransactionProtoParser.h"
+#include "TransactionRingBuffer.h"
 
 using namespace android::surfaceflinger;
 
@@ -41,7 +41,7 @@
 class TransactionTracingTest;
 
 /*
- * Records all committed transactions into a ring bufffer.
+ * Records all committed transactions into a ring buffer.
  *
  * Transactions come in via the binder thread. They are serialized to proto
  * and stored in a map using the transaction id as key. Main thread will
@@ -49,46 +49,129 @@
  * the tracing thread. The tracing thread will then wake up and add the
  * committed transactions to the ring buffer.
  *
- * When generating SF dump state, we will flush the buffer to a file which
- * will then be included in the bugreport.
+ * The traced data can then be collected via:
+ * - Perfetto (preferred).
+ * - File system, after triggering the disk write through SF backdoor. This is legacy and is going
+ *   to be phased out.
+ *
+ * The Perfetto custom data source TransactionDataSource is registered with perfetto and is used
+ * to listen to perfetto events (setup, start, stop, flush) and to write trace packets to perfetto.
+ *
+ * The user can configure/start/stop tracing via /system/bin/perfetto.
+ *
+ * Tracing can operate in the following modes.
+ *
+ * ACTIVE mode:
+ * The transactions ring buffer (starting state + following committed transactions) is written
+ * (only once) to perfetto when the 'start' event is received.
+ * Transactions are then written to perfetto each time they are committed.
+ * On the receiver side, the data source is to be configured to periodically
+ * flush data to disk providing virtually infinite storage.
+ *
+ * CONTINUOUS mode:
+ * Listens to the perfetto 'flush' event (e.g. when a bugreport is taken).
+ * When a 'flush' event is received, the ring buffer of transactions (starting state + following
+ * committed transactions) is written to perfetto. On the receiver side, the data source is to be
+ * configured with a dedicated buffer large enough to store all the flushed data.
+ *
+ *
+ * E.g. start active mode tracing:
+ *
+   adb shell perfetto \
+     -c - --txt \
+     -o /data/misc/perfetto-traces/trace \
+   <<EOF
+   unique_session_name: "surfaceflinger_transactions_active"
+   buffers: {
+       size_kb: 1024
+       fill_policy: RING_BUFFER
+   }
+   data_sources: {
+       config {
+           name: "android.surfaceflinger.transactions"
+           surfaceflinger_transactions_config: {
+               mode: MODE_ACTIVE
+           }
+       }
+   }
+   write_into_file: true
+   file_write_period_ms: 100
+   EOF
+ *
+ *
+ * E.g. start continuous mode tracing:
+ *
+   adb shell perfetto \
+     -c - --txt \
+     -o /data/misc/perfetto-traces/trace \
+   <<EOF
+   unique_session_name: "surfaceflinger_transactions_continuous"
+   buffers: {
+       size_kb: 1024
+       fill_policy: RING_BUFFER
+   }
+   data_sources: {
+       config {
+           name: "android.surfaceflinger.transactions"
+           surfaceflinger_transactions_config: {
+               mode: MODE_CONTINUOUS
+           }
+       }
+   }
+   EOF
  *
  */
 class TransactionTracing {
 public:
+    using Mode = perfetto::protos::pbzero::SurfaceFlingerTransactionsConfig::Mode;
+
     TransactionTracing();
     ~TransactionTracing();
 
+    // Start event from perfetto data source
+    void onStart(Mode mode);
+    // Flush event from perfetto data source
+    void onFlush(Mode mode);
+
     void addQueuedTransaction(const TransactionState&);
     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);
+    // Return buffer contents as trace file proto
+    perfetto::protos::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
     void setBufferSize(size_t bufferSizeInBytes);
     void onLayerRemoved(int layerId);
     void dump(std::string&) const;
     // Wait until all the committed transactions for the specified vsync id are added to the buffer.
     void flush() EXCLUDES(mMainThreadLock);
+
     static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
-    static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
+    static constexpr auto LEGACY_ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
     // version 1 - switching to support new frontend
     static constexpr auto TRACING_VERSION = 1;
 
 private:
+    friend class TransactionTraceWriter;
     friend class TransactionTracingTest;
     friend class SurfaceFlinger;
 
     static constexpr auto DIR_NAME = "/data/misc/wmtrace/";
     static constexpr auto FILE_NAME = "transactions_trace.winscope";
     static constexpr auto FILE_PATH = "/data/misc/wmtrace/transactions_trace.winscope";
+    static std::string getFilePath(const std::string& prefix) {
+        return DIR_NAME + prefix + FILE_NAME;
+    }
 
     mutable std::mutex mTraceLock;
-    RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry> mBuffer
+    TransactionRingBuffer<perfetto::protos::TransactionTraceFile,
+                          perfetto::protos::TransactionTraceEntry>
+            mBuffer GUARDED_BY(mTraceLock);
+    std::unordered_map<uint64_t, perfetto::protos::TransactionState> mQueuedTransactions
             GUARDED_BY(mTraceLock);
-    size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = CONTINUOUS_TRACING_BUFFER_SIZE;
-    std::unordered_map<uint64_t, proto::TransactionState> mQueuedTransactions
-            GUARDED_BY(mTraceLock);
-    LocklessStack<proto::TransactionState> mTransactionQueue;
+    LocklessStack<perfetto::protos::TransactionState> mTransactionQueue;
     nsecs_t mStartingTimestamp GUARDED_BY(mTraceLock);
-    std::unordered_map<int, proto::LayerCreationArgs> mCreatedLayers GUARDED_BY(mTraceLock);
+    std::unordered_map<int, perfetto::protos::LayerCreationArgs> mCreatedLayers
+            GUARDED_BY(mTraceLock);
     std::map<uint32_t /* layerId */, TracingLayerState> mStartingStates GUARDED_BY(mTraceLock);
     frontend::DisplayInfos mStartingDisplayInfos GUARDED_BY(mTraceLock);
 
@@ -118,17 +201,17 @@
     std::vector<uint32_t /* layerId */> mPendingDestroyedLayers; // only accessed by main thread
     int64_t mLastUpdatedVsyncId = -1;
 
-    proto::TransactionTraceFile createTraceFileProto() const;
+    void writeRingBufferToPerfetto(TransactionTracing::Mode mode);
+    perfetto::protos::TransactionTraceFile createTraceFileProto() const;
     void loop();
     void addEntry(const std::vector<CommittedUpdates>& committedTransactions,
                   const std::vector<uint32_t>& removedLayers) EXCLUDES(mTraceLock);
     int32_t getLayerIdLocked(const sp<IBinder>& layerHandle) REQUIRES(mTraceLock);
     void tryPushToTracingThread() EXCLUDES(mMainThreadLock);
-    void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock);
-    void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock);
-    // TEST
-    // Return buffer contents as trace file proto
-    proto::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
+    std::optional<perfetto::protos::TransactionTraceEntry> createStartingStateProtoLocked()
+            REQUIRES(mTraceLock);
+    void updateStartingStateLocked(const perfetto::protos::TransactionTraceEntry& entry)
+            REQUIRES(mTraceLock);
 };
 
 class TransactionTraceWriter : public Singleton<TransactionTraceWriter> {
@@ -138,10 +221,16 @@
 
 public:
     void setWriterFunction(
-            std::function<void(const std::string& prefix, bool overwrite)> function) {
+            std::function<void(const std::string& filename, bool overwrite)> function) {
         mWriterFunction = std::move(function);
     }
-    void invoke(const std::string& prefix, bool overwrite) { mWriterFunction(prefix, overwrite); }
+    void invoke(const std::string& prefix, bool overwrite) {
+        mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite);
+    }
+    /* pass in a complete file path for testing */
+    void invokeForTest(const std::string& filename, bool overwrite) {
+        mWriterFunction(filename, overwrite);
+    }
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
index b6435a8..2ff09c3 100644
--- a/services/surfaceflinger/Tracing/tools/Android.bp
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -31,7 +31,6 @@
     srcs: [
         ":libsurfaceflinger_sources",
         ":libsurfaceflinger_mock_sources",
-        ":layertracegenerator_sources",
         "main.cpp",
     ],
     static_libs: [
@@ -41,15 +40,3 @@
         "libsurfaceflinger_mocks_headers",
     ],
 }
-
-filegroup {
-    name: "layertracegenerator_sources",
-    srcs: [
-        "LayerTraceGenerator.cpp",
-    ],
-}
-
-cc_library_headers {
-    name: "layertracegenerator_headers",
-    export_include_dirs: ["."],
-}
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 519ef44..c2d1954 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -40,8 +40,9 @@
 namespace android {
 using namespace ftl::flag_operators;
 
-bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile,
-                                   const char* outputLayersTracePath, bool onlyLastEntry) {
+bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile,
+                                   std::uint32_t traceFlags, LayerTracing& layerTracing,
+                                   bool onlyLastEntry) {
     if (traceFile.entry_size() == 0) {
         ALOGD("Trace file is empty");
         return false;
@@ -55,23 +56,15 @@
     frontend::LayerSnapshotBuilder snapshotBuilder;
     ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
 
-    renderengine::ShadowSettings globalShadowSettings{.ambientColor = {1, 1, 1, 1}};
+    ShadowSettings globalShadowSettings{.ambientColor = {1, 1, 1, 1}};
     char value[PROPERTY_VALUE_MAX];
     property_get("ro.surface_flinger.supports_background_blur", value, "0");
     bool supportsBlur = atoi(value);
 
-    LayerTracing layerTracing;
-    layerTracing.setTraceFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS);
-    // 10MB buffer size (large enough to hold a single entry)
-    layerTracing.setBufferSize(10 * 1024 * 1024);
-    layerTracing.enable();
-    layerTracing.writeToFile(outputLayersTracePath);
-    std::ofstream out(outputLayersTracePath, std::ios::binary | std::ios::app);
-
     ALOGD("Generating %d transactions...", traceFile.entry_size());
     for (int i = 0; i < traceFile.entry_size(); i++) {
         // parse proto
-        proto::TransactionTraceEntry entry = traceFile.entry(i);
+        perfetto::protos::TransactionTraceEntry entry = traceFile.entry(i);
         ALOGV("    Entry %04d/%04d for time=%" PRId64 " vsyncid=%" PRId64
               " layers +%d -%d handles -%d transactions=%d",
               i, traceFile.entry_size(), entry.elapsed_realtime_nanos(), entry.vsync_id(),
@@ -109,11 +102,11 @@
             ALOGV("       destroyedHandles=%d", entry.destroyed_layers(j));
         }
 
-        std::vector<uint32_t> destroyedHandles;
+        std::vector<std::pair<uint32_t, std::string>> destroyedHandles;
         destroyedHandles.reserve((size_t)entry.destroyed_layer_handles_size());
         for (int j = 0; j < entry.destroyed_layer_handles_size(); j++) {
             ALOGV("       destroyedHandles=%d", entry.destroyed_layer_handles(j));
-            destroyedHandles.push_back(entry.destroyed_layer_handles(j));
+            destroyedHandles.push_back({entry.destroyed_layer_handles(j), ""});
         }
 
         bool displayChanged = entry.displays_changed();
@@ -154,19 +147,26 @@
 
         lifecycleManager.commitChanges();
 
-        LayersProto layersProto = LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {},
-                                                                  layerTracing.getFlags())
-                                          .generate(hierarchyBuilder.getHierarchy());
+        auto layersProto =
+                LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, traceFlags)
+                        .generate(hierarchyBuilder.getHierarchy());
         auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
         if (!onlyLastEntry || (i == traceFile.entry_size() - 1)) {
-            layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(),
-                                entry.vsync_id(), &layersProto, {}, &displayProtos);
-            layerTracing.appendToStream(out);
+            perfetto::protos::LayersSnapshotProto snapshotProto{};
+            snapshotProto.set_vsync_id(entry.vsync_id());
+            snapshotProto.set_elapsed_realtime_nanos(entry.elapsed_realtime_nanos());
+            snapshotProto.set_where(visibleRegionsDirty ? "visibleRegionsDirty" : "bufferLatched");
+            *snapshotProto.mutable_layers() = std::move(layersProto);
+            if ((traceFlags & LayerTracing::TRACE_COMPOSITION) == 0) {
+                snapshotProto.set_excludes_composition_state(true);
+            }
+            *snapshotProto.mutable_displays() = std::move(displayProtos);
+
+            layerTracing.addProtoSnapshotToOstream(std::move(snapshotProto),
+                                                   LayerTracing::Mode::MODE_GENERATED);
         }
     }
-    layerTracing.disable("", /*writeToFile=*/false);
-    out.close();
-    ALOGD("End of generating trace file. File written to %s", outputLayersTracePath);
+    ALOGD("End of generating trace file");
     return true;
 }
 
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
index e41d1e6..e4d02ca 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -18,10 +18,18 @@
 
 #include <Tracing/TransactionTracing.h>
 
+#include <functional>
+#include <optional>
+#include <ostream>
+#include <string>
+
 namespace android {
+
+class LayerTracing;
+
 class LayerTraceGenerator {
 public:
-    bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath,
-                  bool onlyLastEntry);
+    bool generate(const perfetto::protos::TransactionTraceFile&, std::uint32_t traceFlags,
+                  LayerTracing& layerTracing, bool onlyLastEntry = false);
 };
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index 5ca87e4..18022b1 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -21,6 +21,7 @@
 #include <iostream>
 #include <string>
 
+#include <Tracing/LayerTracing.h>
 #include "LayerTraceGenerator.h"
 
 using namespace android;
@@ -41,25 +42,39 @@
         return -1;
     }
 
-    proto::TransactionTraceFile transactionTraceFile;
+    perfetto::protos::TransactionTraceFile transactionTraceFile;
     if (!transactionTraceFile.ParseFromIstream(&input)) {
         std::cout << "Error: Failed to parse " << transactionTracePath;
         return -1;
     }
 
-    const char* outputLayersTracePath =
-            (argc >= 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
+    const auto* outputLayersTracePath =
+            (argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
+    auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::out};
+
+    auto layerTracing = LayerTracing{outStream};
 
     const bool generateLastEntryOnly =
             argc >= 4 && std::string_view(argv[3]) == "--last-entry-only";
 
+    auto traceFlags = LayerTracing::Flag::TRACE_INPUT | LayerTracing::Flag::TRACE_BUFFERS;
+
     ALOGD("Generating %s...", outputLayersTracePath);
     std::cout << "Generating " << outputLayersTracePath << "\n";
 
-    if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath,
+    if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, layerTracing,
                                         generateLastEntryOnly)) {
-        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
+        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath << "\n";
         return -1;
     }
+
+    // Set output file permissions (-rw-r--r--)
+    outStream.close();
+    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    if (chmod(outputLayersTracePath, mode) != 0) {
+        std::cout << "Error: Failed to set permissions of " << outputLayersTracePath << "\n";
+        return -1;
+    }
+
     return 0;
-}
\ No newline at end of file
+}
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 7132a59..31cd2d7 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -89,7 +89,7 @@
     void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
         for (auto state = states.begin(); state != states.end();) {
             if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) {
-                int result = visitor(state->state, state->externalTexture);
+                int result = visitor(*state);
                 if (result == STOP_TRAVERSAL) return;
                 if (result == DELETE_AND_CONTINUE_TRAVERSAL) {
                     state = states.erase(state);
diff --git a/services/surfaceflinger/Utils/FlagUtils.h b/services/surfaceflinger/Utils/FlagUtils.h
new file mode 100644
index 0000000..8435f04
--- /dev/null
+++ b/services/surfaceflinger/Utils/FlagUtils.h
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/properties.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <string>
+
+namespace android::flagutils {
+
+using namespace std::literals::string_literals;
+using namespace com::android::graphics::surfaceflinger;
+
+inline bool vrrConfigEnabled() {
+    static const bool enable_vrr_config =
+            base::GetBoolProperty("debug.sf.enable_vrr_config"s, false);
+    return flags::vrr_config() || enable_vrr_config;
+}
+} // namespace android::flagutils
diff --git a/services/surfaceflinger/Utils/OverlayUtils.h b/services/surfaceflinger/Utils/OverlayUtils.h
new file mode 100644
index 0000000..0ef0be1
--- /dev/null
+++ b/services/surfaceflinger/Utils/OverlayUtils.h
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#pragma once
+
+#include "BackgroundExecutor.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#pragma clang diagnostic pop
+
+#include <gui/SurfaceComposerClient.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+inline constexpr int kDigitWidth = 64;
+inline constexpr int kDigitHeight = 100;
+inline constexpr int kDigitSpace = 16;
+
+// HdrSdrRatioOverlay re-uses this value though it doesn't really need such amount buffer.
+// for output good-looking and code conciseness.
+inline constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
+inline constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
+inline constexpr int kBufferHeight = kDigitHeight;
+
+class SurfaceControl;
+
+// Helper class to delete the SurfaceControl on a helper thread as
+// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
+class SurfaceControlHolder {
+public:
+    explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
+
+    ~SurfaceControlHolder() {
+        // Hand the sp<SurfaceControl> to the helper thread to release the last
+        // reference. This makes sure that the SurfaceControl is destructed without
+        // SurfaceFlinger::mStateLock held.
+        BackgroundExecutor::getInstance().sendCallbacks(
+                {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
+    }
+
+    static std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder(const String8& name) {
+        sp<SurfaceControl> surfaceControl =
+                SurfaceComposerClient::getDefault()
+                        ->createSurface(name, kBufferWidth, kBufferHeight, PIXEL_FORMAT_RGBA_8888,
+                                        ISurfaceComposerClient::eFXSurfaceBufferState);
+        return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+    }
+
+    const sp<SurfaceControl>& get() const { return mSurfaceControl; }
+
+private:
+    sp<SurfaceControl> mSurfaceControl;
+};
+
+// Helper class to draw digit and decimal point.
+class SegmentDrawer {
+public:
+    enum class Segment {
+        Upper,
+        UpperLeft,
+        UpperRight,
+        Middle,
+        LowerLeft,
+        LowerRight,
+        Bottom,
+        DecimalPoint
+    };
+    static void drawSegment(Segment segment, int left, SkColor color, SkCanvas& canvas) {
+        const SkRect rect = [&]() {
+            switch (segment) {
+                case Segment::Upper:
+                    return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
+                case Segment::UpperLeft:
+                    return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2.);
+                case Segment::UpperRight:
+                    return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
+                                            kDigitHeight / 2.);
+                case Segment::Middle:
+                    return SkRect::MakeLTRB(left, kDigitHeight / 2. - kDigitSpace / 2.,
+                                            left + kDigitWidth,
+                                            kDigitHeight / 2. + kDigitSpace / 2.);
+                case Segment::LowerLeft:
+                    return SkRect::MakeLTRB(left, kDigitHeight / 2., left + kDigitSpace,
+                                            kDigitHeight);
+                case Segment::LowerRight:
+                    return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2.,
+                                            left + kDigitWidth, kDigitHeight);
+                case Segment::Bottom:
+                    return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
+                                            kDigitHeight);
+                case Segment::DecimalPoint:
+                    return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitSpace,
+                                            kDigitHeight);
+            }
+        }();
+
+        SkPaint paint;
+        paint.setColor(color);
+        paint.setBlendMode(SkBlendMode::kSrc);
+        canvas.drawRect(rect, paint);
+    }
+
+    static void drawDigit(int digit, int left, SkColor color, SkCanvas& canvas) {
+        if (digit < 0 || digit > 9) return;
+
+        if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
+            digit == 8 || digit == 9)
+            drawSegment(Segment::Upper, left, color, canvas);
+        if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
+            drawSegment(Segment::UpperLeft, left, color, canvas);
+        if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
+            digit == 8 || digit == 9)
+            drawSegment(Segment::UpperRight, left, color, canvas);
+        if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
+            digit == 9)
+            drawSegment(Segment::Middle, left, color, canvas);
+        if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
+            drawSegment(Segment::LowerLeft, left, color, canvas);
+        if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
+            digit == 7 || digit == 8 || digit == 9)
+            drawSegment(Segment::LowerRight, left, color, canvas);
+        if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
+            digit == 9)
+            drawSegment(Segment::Bottom, left, color, canvas);
+    }
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Utils/RingBuffer.h b/services/surfaceflinger/Utils/RingBuffer.h
new file mode 100644
index 0000000..198e7b2
--- /dev/null
+++ b/services/surfaceflinger/Utils/RingBuffer.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <array>
+
+namespace android::utils {
+
+template <class T, size_t SIZE>
+class RingBuffer {
+    RingBuffer(const RingBuffer&) = delete;
+    void operator=(const RingBuffer&) = delete;
+
+public:
+    RingBuffer() = default;
+    ~RingBuffer() = default;
+
+    constexpr size_t capacity() const { return SIZE; }
+
+    size_t size() const { return mCount; }
+
+    T& next() {
+        mHead = static_cast<size_t>(mHead + 1) % SIZE;
+        if (mCount < SIZE) {
+            mCount++;
+        }
+        return mBuffer[static_cast<size_t>(mHead)];
+    }
+
+    T& front() { return (*this)[0]; }
+
+    T& back() { return (*this)[size() - 1]; }
+
+    T& operator[](size_t index) {
+        return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
+    }
+
+    const T& operator[](size_t index) const {
+        return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
+    }
+
+    void clear() {
+        mCount = 0;
+        mHead = -1;
+    }
+
+private:
+    std::array<T, SIZE> mBuffer;
+    int mHead = -1;
+    size_t mCount = 0;
+};
+
+} // namespace android::utils
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index f76a8d7..0f9060d 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -138,3 +138,18 @@
         "surfaceflinger_frametracer_fuzzer.cpp",
     ],
 }
+
+cc_fuzz {
+    name: "surfaceflinger_service_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+        "service_fuzzer_defaults",
+        "fuzzer_disable_leaks",
+    ],
+    srcs: [
+        "surfaceflinger_service_fuzzer.cpp",
+    ],
+    fuzz_config: {
+        triage_assignee: "waghpawan@google.com",
+    },
+}
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index 9fac14e..f22315a 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -597,7 +597,7 @@
                          mFdp.ConsumeBool() ? hal::Vsync::ENABLE : hal::Vsync::DISABLE);
 
     mHwc.isConnected(mPhysicalDisplayId);
-    mHwc.getModes(mPhysicalDisplayId);
+    mHwc.getModes(mPhysicalDisplayId, mFdp.ConsumeIntegral<int32_t>());
     mHwc.getActiveMode(mPhysicalDisplayId);
     mHwc.getColorModes(mPhysicalDisplayId);
     mHwc.hasCapability(mFdp.PickValueInArray(kCapability));
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
index 80943b5..d13189e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -24,7 +24,6 @@
 namespace android::fuzz {
 
 static constexpr LatchUnsignaledConfig kLatchUnsignaledConfig[] = {
-        LatchUnsignaledConfig::Always,
         LatchUnsignaledConfig::AutoSingleLayer,
         LatchUnsignaledConfig::Disabled,
 };
@@ -95,6 +94,7 @@
         BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
         BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER,
         BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER,
+        BnSurfaceComposer::GET_SCHEDULING_POLICY,
 };
 
 static constexpr uint32_t kMinCode = 1000;
@@ -145,9 +145,6 @@
     mFlinger->scheduleRepaint();
     mFlinger->scheduleSample();
 
-    uint32_t texture = mFlinger->getNewTexture();
-    mFlinger->deleteTextureAsync(texture);
-
     sp<IBinder> handle = defaultServiceManager()->checkService(
             String16(mFdp.ConsumeRandomLengthString().c_str()));
     LayerHandle::getLayer(handle);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index ded6ceb..c4077df 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -455,7 +455,8 @@
         result = fdp->ConsumeRandomLengthString().c_str();
         mFlinger->dumpRawDisplayIdentificationData(dumpArgs, result);
 
-        LayersProto layersProto = mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
+        perfetto::protos::LayersProto layersProto =
+                mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
         mFlinger->dumpOffscreenLayersProto(layersProto);
         mFlinger->dumpDisplayProto();
 
@@ -665,7 +666,7 @@
         }
 
         mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
-        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getFps();
+        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getVsyncRate();
         mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
 
         mFlinger->mRefreshRateStats =
@@ -796,6 +797,7 @@
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
+    void onChoreographerAttached() override {}
 
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger =
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 9f0bdde..39a7ee5 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -153,7 +153,8 @@
     native_handle_t* testHandle = native_handle_create(0, 1);
     const bool ownsHandle = mFdp.ConsumeBool();
     sp<NativeHandle> nativeHandle = sp<NativeHandle>::make(testHandle, ownsHandle);
-    layer->setSidebandStream(nativeHandle);
+    layer->setSidebandStream(nativeHandle, getFuzzedFrameTimelineInfo(),
+                             mFdp.ConsumeIntegral<nsecs_t>() /* postTime */);
     layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
 
     layer->fenceHasSignaled();
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 4d1a5ff..a8727f9 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -97,21 +97,26 @@
     return displayId;
 }
 
+struct EventThreadCallback : public IEventThreadCallback {
+    bool throttleVsync(TimePoint, uid_t) override { return false; }
+    Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
+    void resync() override {}
+};
+
 void SchedulerFuzzer::fuzzEventThread() {
     mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
             new scheduler::VsyncSchedule(getPhysicalDisplayId(),
                                          std::make_shared<mock::VSyncTracker>(),
                                          std::make_shared<mock::VSyncDispatch>(), nullptr));
-    const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
+    EventThreadCallback callback;
     std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
-            android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
+            android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, callback,
                                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
                                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
 
     thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
     sp<EventThreadConnection> connection =
-            sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>(),
-                                            nullptr);
+            sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>());
     thread->requestNextVsync(connection);
     thread->setVsyncRate(mFdp.ConsumeIntegral<uint32_t>() /*rate*/, connection);
 
@@ -177,8 +182,9 @@
     uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
     uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
-    scheduler::VSyncPredictor tracker{kDisplayId, mFdp.ConsumeIntegral<uint16_t>() /*period*/,
-                                      historySize, minimumSamplesForPrediction,
+    nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
+    scheduler::VSyncPredictor tracker{kDisplayId, idealPeriod, historySize,
+                                      minimumSamplesForPrediction,
                                       mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
     uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
     tracker.setPeriod(period);
@@ -390,7 +396,7 @@
                                       PowerMode::OFF);
 
     const auto fpsOpt = displayModes.get(modeId).transform(
-            [](const DisplayModePtr& mode) { return mode->getFps(); });
+            [](const DisplayModePtr& mode) { return mode->getVsyncRate(); });
     refreshRateStats.setRefreshRate(*fpsOpt);
 
     refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes));
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_service_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_service_fuzzer.cpp
new file mode 100644
index 0000000..849a896
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_service_fuzzer.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fuzzbinder/libbinder_driver.h>
+
+#include "SurfaceFlinger.h"
+#include "SurfaceFlingerDefaultFactory.h"
+
+using namespace android;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    DefaultFactory factory;
+    sp<SurfaceFlinger> flinger = sp<SurfaceFlinger>::make(factory);
+    flinger->init();
+
+    sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
+    fuzzService({flinger, composerAIDL}, FuzzedDataProvider(data, size));
+    return 0;
+}
diff --git a/services/surfaceflinger/layerproto/Android.bp b/services/surfaceflinger/layerproto/Android.bp
index 7287dd0..a4dc8a0 100644
--- a/services/surfaceflinger/layerproto/Android.bp
+++ b/services/surfaceflinger/layerproto/Android.bp
@@ -13,7 +13,20 @@
 
     srcs: [
         "LayerProtoParser.cpp",
-        "*.proto",
+    ],
+
+    static_libs: [
+        "libperfetto_client_experimental",
+    ],
+
+    whole_static_libs: [
+        // TODO(b/169779783): move  into "static_libs" when the soong issue is fixed
+        "perfetto_trace_protos",
+    ],
+
+    export_static_lib_headers: [
+        "libperfetto_client_experimental",
+        "perfetto_trace_protos",
     ],
 
     shared_libs: [
@@ -24,10 +37,6 @@
         "libbase",
     ],
 
-    proto: {
-        export_proto_headers: true,
-    },
-
     cppflags: [
         "-Werror",
         "-Wno-unused-parameter",
@@ -42,22 +51,3 @@
         "-Wno-undef",
     ],
 }
-
-java_library_static {
-    name: "layersprotoslite",
-    host_supported: true,
-    proto: {
-        type: "lite",
-        include_dirs: ["external/protobuf/src"],
-    },
-    srcs: ["*.proto"],
-    sdk_version: "core_platform",
-    target: {
-        android: {
-            jarjar_rules: "jarjar-rules.txt",
-        },
-        host: {
-            static_libs: ["libprotobuf-java-lite"],
-        },
-    },
-}
diff --git a/services/surfaceflinger/layerproto/LayerProtoParser.cpp b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
index 854084e..c3d0a40 100644
--- a/services/surfaceflinger/layerproto/LayerProtoParser.cpp
+++ b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
@@ -37,7 +37,8 @@
     return lhs->id < rhs->id;
 }
 
-LayerProtoParser::LayerTree LayerProtoParser::generateLayerTree(const LayersProto& layersProto) {
+LayerProtoParser::LayerTree LayerProtoParser::generateLayerTree(
+        const perfetto::protos::LayersProto& layersProto) {
     LayerTree layerTree;
     layerTree.allLayers = generateLayerList(layersProto);
 
@@ -53,7 +54,7 @@
 }
 
 std::vector<LayerProtoParser::Layer> LayerProtoParser::generateLayerList(
-        const LayersProto& layersProto) {
+        const perfetto::protos::LayersProto& layersProto) {
     std::vector<Layer> layerList;
     std::unordered_map<int32_t, Layer*> layerMap;
 
@@ -74,7 +75,8 @@
     return layerList;
 }
 
-LayerProtoParser::Layer LayerProtoParser::generateLayer(const LayerProto& layerProto) {
+LayerProtoParser::Layer LayerProtoParser::generateLayer(
+        const perfetto::protos::LayerProto& layerProto) {
     Layer layer;
     layer.id = layerProto.id();
     layer.name = layerProto.name();
@@ -120,17 +122,19 @@
     return layer;
 }
 
-LayerProtoParser::Region LayerProtoParser::generateRegion(const RegionProto& regionProto) {
+LayerProtoParser::Region LayerProtoParser::generateRegion(
+        const perfetto::protos::RegionProto& regionProto) {
     LayerProtoParser::Region region;
     for (int i = 0; i < regionProto.rect_size(); i++) {
-        const RectProto& rectProto = regionProto.rect(i);
+        const perfetto::protos::RectProto& rectProto = regionProto.rect(i);
         region.rects.push_back(generateRect(rectProto));
     }
 
     return region;
 }
 
-LayerProtoParser::Rect LayerProtoParser::generateRect(const RectProto& rectProto) {
+LayerProtoParser::Rect LayerProtoParser::generateRect(
+        const perfetto::protos::RectProto& rectProto) {
     LayerProtoParser::Rect rect;
     rect.left = rectProto.left();
     rect.top = rectProto.top();
@@ -140,7 +144,8 @@
     return rect;
 }
 
-LayerProtoParser::FloatRect LayerProtoParser::generateFloatRect(const FloatRectProto& rectProto) {
+LayerProtoParser::FloatRect LayerProtoParser::generateFloatRect(
+        const perfetto::protos::FloatRectProto& rectProto) {
     LayerProtoParser::FloatRect rect;
     rect.left = rectProto.left();
     rect.top = rectProto.top();
@@ -151,7 +156,7 @@
 }
 
 LayerProtoParser::Transform LayerProtoParser::generateTransform(
-        const TransformProto& transformProto) {
+        const perfetto::protos::TransformProto& transformProto) {
     LayerProtoParser::Transform transform;
     transform.dsdx = transformProto.dsdx();
     transform.dtdx = transformProto.dtdx();
@@ -162,7 +167,7 @@
 }
 
 LayerProtoParser::ActiveBuffer LayerProtoParser::generateActiveBuffer(
-        const ActiveBufferProto& activeBufferProto) {
+        const perfetto::protos::ActiveBufferProto& activeBufferProto) {
     LayerProtoParser::ActiveBuffer activeBuffer;
     activeBuffer.width = activeBufferProto.width();
     activeBuffer.height = activeBufferProto.height();
@@ -172,7 +177,7 @@
     return activeBuffer;
 }
 
-void LayerProtoParser::updateChildrenAndRelative(const LayerProto& layerProto,
+void LayerProtoParser::updateChildrenAndRelative(const perfetto::protos::LayerProto& layerProto,
                                                  std::unordered_map<int32_t, Layer*>& layerMap) {
     auto currLayer = layerMap[layerProto.id()];
 
@@ -188,13 +193,13 @@
         }
     }
 
-    if (layerProto.parent() != -1) {
+    if (layerProto.has_parent()) {
         if (layerMap.count(layerProto.parent()) > 0) {
             currLayer->parent = layerMap[layerProto.parent()];
         }
     }
 
-    if (layerProto.z_order_relative_of() != -1) {
+    if (layerProto.has_z_order_relative_of()) {
         if (layerMap.count(layerProto.z_order_relative_of()) > 0) {
             currLayer->zOrderRelativeOf = layerMap[layerProto.z_order_relative_of()];
         }
diff --git a/services/surfaceflinger/layerproto/common.proto b/services/surfaceflinger/layerproto/common.proto
deleted file mode 100644
index 5e20d4d..0000000
--- a/services/surfaceflinger/layerproto/common.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-package android.surfaceflinger;
-
-message RegionProto {
-    reserved 1; // Previously: uint64 id
-    repeated RectProto rect = 2;
-}
-
-message RectProto {
-  int32 left   = 1;
-  int32 top    = 2;
-  int32 right  = 3;
-  int32 bottom = 4;
-}
-
-message SizeProto {
-  int32 w = 1;
-  int32 h = 2;
-}
-
-message TransformProto {
-  float dsdx = 1;
-  float dtdx = 2;
-  float dsdy = 3;
-  float dtdy = 4;
-  int32 type = 5;
-}
-
-message ColorProto {
-    float r = 1;
-    float g = 2;
-    float b = 3;
-    float a = 4;
-}
-
-message InputWindowInfoProto {
-    uint32 layout_params_flags = 1;
-    int32 layout_params_type = 2;
-    RectProto frame = 3;
-    RegionProto touchable_region = 4;
-
-    int32 surface_inset = 5;
-    bool visible = 6;
-    bool can_receive_keys = 7 [deprecated = true];
-    bool focusable = 8;
-    bool has_wallpaper = 9;
-
-    float global_scale_factor = 10;
-    float window_x_scale = 11 [deprecated = true];
-    float window_y_scale = 12 [deprecated = true];
-
-    int32 crop_layer_id = 13;
-    bool replace_touchable_region_with_crop = 14;
-    RectProto touchable_region_crop = 15;
-    TransformProto transform = 16;
-    uint32 input_config = 17;
-}
-
-message BlurRegion {
-    uint32 blur_radius = 1;
-    uint32 corner_radius_tl = 2;
-    uint32 corner_radius_tr = 3;
-    uint32 corner_radius_bl = 4;
-    float corner_radius_br = 5;
-    float alpha = 6;
-    int32 left = 7;
-    int32 top = 8;
-    int32 right = 9;
-    int32 bottom = 10;
-}
-
-message ColorTransformProto {
-    // This will be a 4x4 matrix of float values
-    repeated float val = 1;
-}
diff --git a/services/surfaceflinger/layerproto/display.proto b/services/surfaceflinger/layerproto/display.proto
deleted file mode 100644
index c8cd926..0000000
--- a/services/surfaceflinger/layerproto/display.proto
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
-
-package android.surfaceflinger;
-
-message DisplayProto {
-    uint64 id = 1;
-
-    string name = 2;
-
-    uint32 layer_stack = 3;
-
-    SizeProto size = 4;
-
-    RectProto layer_stack_space_rect = 5;
-
-    TransformProto transform = 6;
-
-    bool is_virtual = 7;
-}
diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h
index f560562..4a2ef3d 100644
--- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h
+++ b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+#pragma once
+
 // pragma is used here to disable the warnings emitted from the protobuf
 // headers. By adding #pragma before including layer.pb.h, it supresses
 // protobuf warnings, but allows the rest of the files to continuing using
 // the current flags.
 // This file should be included instead of directly including layer.b.h
 #pragma GCC system_header
-#include <layers.pb.h>
-#include <layerstrace.pb.h>
+#include <perfetto/config/android/surfaceflinger_layers_config.pbzero.h>
+#include <perfetto/trace/android/surfaceflinger_layers.pb.h>
+#include <perfetto/trace/android/surfaceflinger_layers.pbzero.h>
diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
index cdc2706..79c3982 100644
--- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
+++ b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
@@ -131,19 +131,22 @@
         std::vector<Layer*> topLevelLayers;
     };
 
-    static LayerTree generateLayerTree(const LayersProto& layersProto);
+    static LayerTree generateLayerTree(const perfetto::protos::LayersProto& layersProto);
     static std::string layerTreeToString(const LayerTree& layerTree);
 
 private:
-    static std::vector<Layer> generateLayerList(const LayersProto& layersProto);
-    static LayerProtoParser::Layer generateLayer(const LayerProto& layerProto);
-    static LayerProtoParser::Region generateRegion(const RegionProto& regionProto);
-    static LayerProtoParser::Rect generateRect(const RectProto& rectProto);
-    static LayerProtoParser::FloatRect generateFloatRect(const FloatRectProto& rectProto);
-    static LayerProtoParser::Transform generateTransform(const TransformProto& transformProto);
+    static std::vector<Layer> generateLayerList(const perfetto::protos::LayersProto& layersProto);
+    static LayerProtoParser::Layer generateLayer(const perfetto::protos::LayerProto& layerProto);
+    static LayerProtoParser::Region generateRegion(
+            const perfetto::protos::RegionProto& regionProto);
+    static LayerProtoParser::Rect generateRect(const perfetto::protos::RectProto& rectProto);
+    static LayerProtoParser::FloatRect generateFloatRect(
+            const perfetto::protos::FloatRectProto& rectProto);
+    static LayerProtoParser::Transform generateTransform(
+            const perfetto::protos::TransformProto& transformProto);
     static LayerProtoParser::ActiveBuffer generateActiveBuffer(
-            const ActiveBufferProto& activeBufferProto);
-    static void updateChildrenAndRelative(const LayerProto& layerProto,
+            const perfetto::protos::ActiveBufferProto& activeBufferProto);
+    static void updateChildrenAndRelative(const perfetto::protos::LayerProto& layerProto,
                                           std::unordered_map<int32_t, Layer*>& layerMap);
 
     static std::string layerToString(const LayerProtoParser::Layer* layer);
diff --git a/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h b/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
index 3e9ca52..ea80ad8 100644
--- a/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
+++ b/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
+#pragma once
+
 // disable the warnings emitted from the protobuf headers. This file should be included instead of
 // directly including the generated header file
 #pragma GCC system_header
-#include <transactions.pb.h>
+#include <perfetto/config/android/surfaceflinger_transactions_config.pbzero.h>
+#include <perfetto/trace/android/surfaceflinger_transactions.pb.h>
+#include <perfetto/trace/android/surfaceflinger_transactions.pbzero.h>
diff --git a/services/surfaceflinger/layerproto/jarjar-rules.txt b/services/surfaceflinger/layerproto/jarjar-rules.txt
deleted file mode 100644
index 40043a8..0000000
--- a/services/surfaceflinger/layerproto/jarjar-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto
deleted file mode 100644
index e9add2e..0000000
--- a/services/surfaceflinger/layerproto/layers.proto
+++ /dev/null
@@ -1,171 +0,0 @@
-// Definitions for SurfaceFlinger layers.
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
-
-package android.surfaceflinger;
-
-// Contains a list of all layers.
-message LayersProto {
-  repeated LayerProto layers = 1;
-}
-
-// Must match definition in the IComposerClient HAL
-enum HwcCompositionType {
-    // Invalid composition type
-    INVALID = 0;
-    // Layer was composited by the client into the client target buffer
-    CLIENT = 1;
-    // Layer was composited by the device through hardware overlays
-    DEVICE = 2;
-    // Layer was composited by the device using a color
-    SOLID_COLOR = 3;
-    // Similar to DEVICE, but the layer position may have been asynchronously set
-    // through setCursorPosition
-    CURSOR = 4;
-    // Layer was composited by the device via a sideband stream.
-    SIDEBAND = 5;
-}
-
-// Information about each layer.
-message LayerProto {
-  // unique id per layer.
-  int32 id = 1;
-  // unique name per layer.
-  string name = 2;
-  // list of children this layer may have. May be empty.
-  repeated int32 children = 3;
-  // list of layers that are z order relative to this layer.
-  repeated int32 relatives = 4;
-  // The type of layer, ex Color, Layer
-  string type = 5;
-  RegionProto transparent_region = 6;
-  RegionProto visible_region = 7;
-  RegionProto damage_region = 8;
-  uint32 layer_stack = 9;
-  // The layer's z order. Can be z order in layer stack, relative to parent,
-  // or relative to another layer specified in zOrderRelative.
-  int32 z = 10;
-  // The layer's position on the display.
-  PositionProto position = 11;
-  // The layer's requested position.
-  PositionProto requested_position = 12;
-  // The layer's size.
-  SizeProto size = 13;
-  // The layer's crop in it's own bounds.
-  RectProto crop = 14;
-  // The layer's crop in it's parent's bounds.
-  RectProto final_crop = 15 [deprecated=true];
-  bool is_opaque = 16;
-  bool invalidate = 17;
-  string dataspace = 18;
-  string pixel_format = 19;
-  // The layer's actual color.
-  ColorProto color = 20;
-  // The layer's requested color.
-  ColorProto requested_color = 21;
-  // Can be any combination of
-  //    hidden = 0x01
-  //    opaque = 0x02,
-  //    secure = 0x80,
-  uint32 flags = 22;
-  // The layer's actual transform
-  TransformProto transform = 23;
-  // The layer's requested transform.
-  TransformProto requested_transform = 24;
-  // The parent layer. This value can be null if there is no parent.
-  int32 parent = 25;
-  // The layer that this layer has a z order relative to. This value can be null.
-  int32 z_order_relative_of = 26;
-  // This value can be null if there's nothing to draw.
-  ActiveBufferProto active_buffer = 27;
-  // The number of frames available.
-  int32 queued_frames = 28;
-  bool refresh_pending = 29;
-  // The layer's composer backend destination frame
-  RectProto hwc_frame = 30;
-  // The layer's composer backend source crop
-  FloatRectProto hwc_crop = 31;
-  // The layer's composer backend transform
-  int32 hwc_transform = 32;
-  int32 window_type = 33 [deprecated=true];
-  int32 app_id = 34 [deprecated=true];
-  // The layer's composition type
-  HwcCompositionType hwc_composition_type = 35;
-  // If it's a buffer layer, indicate if the content is protected
-  bool is_protected = 36;
-  // Current frame number being rendered.
-  uint64 curr_frame = 37;
-  // A list of barriers that the layer is waiting to update state.
-  repeated BarrierLayerProto barrier_layer = 38;
-  // If active_buffer is not null, record its transform.
-  TransformProto buffer_transform = 39;
-  int32 effective_scaling_mode = 40;
-  // Layer's corner radius.
-  float corner_radius = 41;
-  // Metadata map. May be empty.
-  map<int32, bytes> metadata = 42;
-
-  TransformProto effective_transform = 43;
-  FloatRectProto source_bounds = 44;
-  FloatRectProto bounds = 45;
-  FloatRectProto screen_bounds = 46;
-
-  InputWindowInfoProto input_window_info = 47;
-
-  // Crop used to draw the rounded corner.
-  FloatRectProto corner_radius_crop = 48;
-
-  // length of the shadow to draw around the layer, it may be set on the
-  // layer or set by a parent layer.
-  float shadow_radius = 49;
-  ColorTransformProto color_transform = 50;
-
-  bool is_relative_of = 51;
-  // Layer's background blur radius in pixels.
-  int32 background_blur_radius = 52;
-
-  uint32 owner_uid = 53;
-
-  // Regions of a layer, where blur should be applied.
-  repeated BlurRegion blur_regions = 54;
-
-  bool is_trusted_overlay = 55;
-
-  // Corner radius explicitly set on layer rather than inherited
-  float requested_corner_radius = 56;
-
-  RectProto destination_frame = 57;
-
-  uint32 original_id = 58;
-}
-
-message PositionProto {
-  float x = 1;
-  float y = 2;
-}
-
-message FloatRectProto {
-  float left = 1;
-  float top = 2;
-  float right = 3;
-  float bottom = 4;
-}
-
-message ActiveBufferProto {
-  uint32 width = 1;
-  uint32 height = 2;
-  uint32 stride = 3;
-  int32 format = 4;
-  uint64 usage = 5;
-}
-
-message BarrierLayerProto {
-  // layer id the barrier is waiting on.
-  int32 id = 1;
-  // frame number the barrier is waiting on.
-  uint64 frame_number = 2;
-}
-
diff --git a/services/surfaceflinger/layerproto/layerstrace.proto b/services/surfaceflinger/layerproto/layerstrace.proto
deleted file mode 100644
index 804a499..0000000
--- a/services/surfaceflinger/layerproto/layerstrace.proto
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/layers.proto";
-import "frameworks/native/services/surfaceflinger/layerproto/display.proto";
-
-package android.surfaceflinger;
-
-/* represents a file full of surface flinger trace entries.
-   Encoded, it should start with 0x4c 0x59 0x52 0x54 0x52 0x41 0x43 0x45 (.LYRTRACE), such
-   that they can be easily identified. */
-message LayersTraceFileProto {
-
-    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
-       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
-        constants into .proto files. */
-    enum MagicNumber {
-        INVALID = 0;
-        MAGIC_NUMBER_L = 0x5452594c;  /* LYRT (little-endian ASCII) */
-        MAGIC_NUMBER_H = 0x45434152;  /* RACE (little-endian ASCII) */
-    }
-
-    optional fixed64 magic_number = 1;  /* Must be the first field, set to value in MagicNumber */
-    repeated LayersTraceProto entry = 2;
-
-    /* offset between real-time clock and elapsed time clock in nanoseconds.
-       Calculated as: systemTime(SYSTEM_TIME_REALTIME) - systemTime(SYSTEM_TIME_MONOTONIC) */
-    optional fixed64 real_to_elapsed_time_offset_nanos = 3;
-}
-
-/* one layers trace entry. */
-message LayersTraceProto {
-    /* required: elapsed realtime in nanos since boot of when this entry was logged */
-    optional sfixed64 elapsed_realtime_nanos = 1;
-
-    /* where the trace originated */
-    optional string where = 2;
-
-    optional LayersProto layers = 3;
-
-    // Blob for the current HWC information for all layers, reported by dumpsys.
-    optional string hwc_blob = 4;
-
-    /* Includes state sent during composition like visible region and composition type. */
-    optional bool excludes_composition_state = 5;
-
-    /* Number of missed entries since the last entry was recorded. */
-    optional uint32 missed_entries = 6;
-
-    repeated DisplayProto displays = 7;
-
-    optional int64 vsync_id = 8;
-}
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
deleted file mode 100644
index d03afa0..0000000
--- a/services/surfaceflinger/layerproto/transactions.proto
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
-
-package android.surfaceflinger.proto;
-
-/* Represents a file full of surface flinger transactions.
-   Encoded, it should start with 0x54 0x4E 0x58 0x54 0x52 0x41 0x43 0x45 (.TNXTRACE), such
-   that they can be easily identified. */
-message TransactionTraceFile {
-    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
-       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
-        constants into .proto files. */
-    enum MagicNumber {
-        INVALID = 0;
-        MAGIC_NUMBER_L = 0x54584E54; /* TNXT (little-endian ASCII) */
-        MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
-    }
-
-    fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
-    repeated TransactionTraceEntry entry = 2;
-
-    /* offset between real-time clock and elapsed time clock in nanoseconds.
-       Calculated as: systemTime(SYSTEM_TIME_REALTIME) - systemTime(SYSTEM_TIME_MONOTONIC) */
-    fixed64 real_to_elapsed_time_offset_nanos = 3;
-    uint32 version = 4;
-}
-
-message TransactionTraceEntry {
-    int64 elapsed_realtime_nanos = 1;
-    int64 vsync_id = 2;
-    repeated TransactionState transactions = 3;
-    repeated LayerCreationArgs added_layers = 4;
-    repeated uint32 destroyed_layers = 5;
-    repeated DisplayState added_displays = 6;
-    repeated int32 removed_displays = 7;
-    repeated uint32 destroyed_layer_handles = 8;
-    bool displays_changed = 9;
-    repeated DisplayInfo displays = 10;
-}
-
-message DisplayInfo {
-    uint32 layer_stack = 1;
-    int32 display_id = 2;
-    int32 logical_width = 3;
-    int32 logical_height = 4;
-    Transform transform_inverse = 5;
-    Transform transform = 6;
-    bool receives_input = 7;
-    bool is_secure = 8;
-    bool is_primary = 9;
-    bool is_virtual = 10;
-    int32 rotation_flags = 11;
-    int32 transform_hint = 12;
-
-}
-
-message LayerCreationArgs {
-    uint32 layer_id = 1;
-    string name = 2;
-    uint32 flags = 3;
-    uint32 parent_id = 4;
-    uint32 mirror_from_id = 5;
-    bool add_to_root = 6;
-    uint32 layer_stack_to_mirror = 7;
-}
-
-message Transform {
-    float dsdx = 1;
-    float dtdx = 2;
-    float dtdy = 3;
-    float dsdy = 4;
-    float tx = 5;
-    float ty = 6;
-}
-
-message TransactionState {
-    int32 pid = 1;
-    int32 uid = 2;
-    int64 vsync_id = 3;
-    int32 input_event_id = 4;
-    int64 post_time = 5;
-    uint64 transaction_id = 6;
-    repeated LayerState layer_changes = 7;
-    repeated DisplayState display_changes = 8;
-    repeated uint64 merged_transaction_ids = 9;
-}
-
-// Keep insync with layer_state_t
-message LayerState {
-    uint32 layer_id = 1;
-    // Changes are split into ChangesLsb and ChangesMsb. First 32 bits are in ChangesLsb
-    // and the next 32 bits are in ChangesMsb. This is needed because enums have to be
-    // 32 bits and there's no nice way to put 64bit constants into .proto files.
-    enum ChangesLsb {
-        eChangesLsbNone = 0;
-        ePositionChanged = 0x00000001;
-        eLayerChanged = 0x00000002;
-        // unused = 0x00000004;
-        eAlphaChanged = 0x00000008;
-
-        eMatrixChanged = 0x00000010;
-        eTransparentRegionChanged = 0x00000020;
-        eFlagsChanged = 0x00000040;
-        eLayerStackChanged = 0x00000080;
-
-        eReleaseBufferListenerChanged = 0x00000400;
-        eShadowRadiusChanged = 0x00000800;
-
-        eBufferCropChanged = 0x00002000;
-        eRelativeLayerChanged = 0x00004000;
-        eReparent = 0x00008000;
-
-        eColorChanged = 0x00010000;
-        eBufferTransformChanged = 0x00040000;
-        eTransformToDisplayInverseChanged = 0x00080000;
-
-        eCropChanged = 0x00100000;
-        eBufferChanged = 0x00200000;
-        eAcquireFenceChanged = 0x00400000;
-        eDataspaceChanged = 0x00800000;
-
-        eHdrMetadataChanged = 0x01000000;
-        eSurfaceDamageRegionChanged = 0x02000000;
-        eApiChanged = 0x04000000;
-        eSidebandStreamChanged = 0x08000000;
-
-        eColorTransformChanged = 0x10000000;
-        eHasListenerCallbacksChanged = 0x20000000;
-        eInputInfoChanged = 0x40000000;
-        eCornerRadiusChanged = -2147483648; // 0x80000000; (proto stores enums as signed int)
-    };
-    enum ChangesMsb {
-        eChangesMsbNone = 0;
-        eDestinationFrameChanged = 0x1;
-        eCachedBufferChanged = 0x2;
-        eBackgroundColorChanged = 0x4;
-        eMetadataChanged = 0x8;
-        eColorSpaceAgnosticChanged = 0x10;
-        eFrameRateSelectionPriority = 0x20;
-        eFrameRateChanged = 0x40;
-        eBackgroundBlurRadiusChanged = 0x80;
-        eProducerDisconnect = 0x100;
-        eFixedTransformHintChanged = 0x200;
-        eFrameNumberChanged = 0x400;
-        eBlurRegionsChanged = 0x800;
-        eAutoRefreshChanged = 0x1000;
-        eStretchChanged = 0x2000;
-        eTrustedOverlayChanged = 0x4000;
-        eDropInputModeChanged = 0x8000;
-    };
-    uint64 what = 2;
-    float x = 3;
-    float y = 4;
-    int32 z = 5;
-    uint32 w = 6;
-    uint32 h = 7;
-    uint32 layer_stack = 8;
-
-    enum Flags {
-        eFlagsNone = 0;
-        eLayerHidden = 0x01;
-        eLayerOpaque = 0x02;
-        eLayerSkipScreenshot = 0x40;
-        eLayerSecure = 0x80;
-        eEnableBackpressure = 0x100;
-        eLayerIsDisplayDecoration = 0x200;
-    };
-    uint32 flags = 9;
-    uint32 mask = 10;
-
-    message Matrix22 {
-        float dsdx = 1;
-        float dtdx = 2;
-        float dtdy = 3;
-        float dsdy = 4;
-    };
-    Matrix22 matrix = 11;
-    float corner_radius = 12;
-    uint32 background_blur_radius = 13;
-    uint32 parent_id = 14;
-    uint32 relative_parent_id = 15;
-
-    float alpha = 16;
-    message Color3 {
-        float r = 1;
-        float g = 2;
-        float b = 3;
-    }
-    Color3 color = 17;
-    RegionProto transparent_region = 18;
-    uint32 transform = 19;
-    bool transform_to_display_inverse = 20;
-    RectProto crop = 21;
-
-    message BufferData {
-        uint64 buffer_id = 1;
-        uint32 width = 2;
-        uint32 height = 3;
-        uint64 frame_number = 4;
-
-        enum BufferDataChange {
-            BufferDataChangeNone = 0;
-            fenceChanged = 0x01;
-            frameNumberChanged = 0x02;
-            cachedBufferChanged = 0x04;
-        }
-        uint32 flags = 5;
-        uint64 cached_buffer_id = 6;
-
-        enum PixelFormat {
-            PIXEL_FORMAT_UNKNOWN = 0;
-            PIXEL_FORMAT_CUSTOM = -4;
-            PIXEL_FORMAT_TRANSLUCENT = -3;
-            PIXEL_FORMAT_TRANSPARENT = -2;
-            PIXEL_FORMAT_OPAQUE = -1;
-            PIXEL_FORMAT_RGBA_8888 = 1;
-            PIXEL_FORMAT_RGBX_8888 = 2;
-            PIXEL_FORMAT_RGB_888 = 3;
-            PIXEL_FORMAT_RGB_565 = 4;
-            PIXEL_FORMAT_BGRA_8888 = 5;
-            PIXEL_FORMAT_RGBA_5551 = 6;
-            PIXEL_FORMAT_RGBA_4444 = 7;
-            PIXEL_FORMAT_RGBA_FP16 = 22;
-            PIXEL_FORMAT_RGBA_1010102 = 43;
-            PIXEL_FORMAT_R_8 = 0x38;
-        }
-        PixelFormat pixel_format = 7;
-        uint64 usage = 8;
-    }
-    BufferData buffer_data = 22;
-    int32 api = 23;
-    bool has_sideband_stream = 24;
-    ColorTransformProto color_transform = 25;
-    repeated BlurRegion blur_regions = 26;
-
-    message WindowInfo {
-        uint32 layout_params_flags = 1;
-        int32 layout_params_type = 2;
-        RegionProto touchable_region = 3;
-        int32 surface_inset = 4;
-        bool focusable = 5; // unused
-        bool has_wallpaper = 6; // unused
-        float global_scale_factor = 7;
-        uint32 crop_layer_id = 8;
-        bool replace_touchable_region_with_crop = 9;
-        RectProto touchable_region_crop = 10;
-        Transform transform = 11;
-        uint32 input_config = 12;
-    }
-    WindowInfo window_info_handle = 27;
-    float bg_color_alpha = 28;
-    int32 bg_color_dataspace = 29;
-    bool color_space_agnostic = 30;
-    float shadow_radius = 31;
-    int32 frame_rate_selection_priority = 32;
-    float frame_rate = 33;
-    int32 frame_rate_compatibility = 34;
-    int32 change_frame_rate_strategy = 35;
-    uint32 fixed_transform_hint = 36;
-    uint64 frame_number = 37;
-    bool auto_refresh = 38;
-    bool is_trusted_overlay = 39;
-    RectProto buffer_crop = 40;
-    RectProto destination_frame = 41;
-
-    enum DropInputMode {
-        NONE = 0;
-        ALL = 1;
-        OBSCURED = 2;
-    };
-    DropInputMode drop_input_mode = 42;
-}
-
-message DisplayState {
-    enum Changes {
-        eChangesNone = 0;
-        eSurfaceChanged = 0x01;
-        eLayerStackChanged = 0x02;
-        eDisplayProjectionChanged = 0x04;
-        eDisplaySizeChanged = 0x08;
-        eFlagsChanged = 0x10;
-    };
-    int32 id = 1;
-    uint32 what = 2;
-    uint32 flags = 3;
-    uint32 layer_stack = 4;
-    uint32 orientation = 5;
-    RectProto layer_stack_space_rect = 6;
-    RectProto oriented_display_space_rect = 7;
-    uint32 width = 8;
-    uint32 height = 9;
-}
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index cf23169..9599452 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -149,6 +149,9 @@
 
     // publish gui::ISurfaceComposer, the new AIDL interface
     sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
+    if (flags::misc1()) {
+        composerAIDL->setMinSchedulerPolicy(SCHED_FIFO, newPriority);
+    }
     sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false,
                    IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
 
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
new file mode 100644
index 0000000..19d194f
--- /dev/null
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -0,0 +1,55 @@
+package: "com.android.graphics.surfaceflinger.flags"
+
+flag {
+  name: "misc1"
+  namespace: "core_graphics"
+  description: "This flag controls minor miscellaneous SurfaceFlinger changes"
+  bug: "297389311"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "connected_display"
+  namespace: "core_graphics"
+  description: "Controls SurfaceFlinger support for Connected Displays in 24Q1"
+  bug: "278199093"
+  is_fixed_read_only: true
+}
+
+flag{
+  name: "late_boot_misc2"
+  namespace: "core_graphics"
+  description: "This flag controls minor miscellaneous SurfaceFlinger changes. Cannot be read before boot finished!"
+  bug: "297389311"
+}
+
+flag {
+  name: "vrr_config"
+  namespace: "core_graphics"
+  description: "Controls SurfaceFlinger support for VRR Configurations"
+  bug: "284845445"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "dont_skip_on_early"
+  namespace: "core_graphics"
+  description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
+  bug: "273702768"
+}
+
+flag {
+  name: "multithreaded_present"
+  namespace: "core_graphics"
+  description: "Controls whether to offload present calls to another thread"
+  bug: "259132483"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "enable_small_area_detection"
+  namespace: "core_graphics"
+  description: "Feature flag for SmallAreaDetection"
+  bug: "283055450"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 689f51a..be29be4 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -199,6 +199,7 @@
 # useColorManagement indicates whether SurfaceFlinger should manage color
 # by switching to appropriate color mode automatically depending on the
 # Dataspace of the surfaces on screen.
+# DEPRECATED: SurfaceFlinger is always color managed.
 prop {
     api_name: "use_color_management"
     type: Boolean
@@ -207,7 +208,7 @@
     prop_name: "ro.surface_flinger.use_color_management"
 }
 
-# The following four propertiess define:
+# The following four properties define:
 # Returns the default data space and pixel format that SurfaceFlinger
 # expects to receive and output as well as the wide color gamut data space
 # and pixel format for wide color gamut surfaces.
@@ -276,7 +277,7 @@
 # The variable works only when useColorManagement is specified. If
 # unspecified, the data space follows what SurfaceFlinger expects for
 # surfaces when useColorManagement is specified.
-
+# DEPRECATED: do not use
 prop {
     api_name: "color_space_agnostic_dataspace"
     type: Long
@@ -472,6 +473,16 @@
     prop_name: "ro.surface_flinger.ignore_hdr_camera_layers"
 }
 
+# Controls the minimum acquired buffers SurfaceFlinger will suggest via
+# ISurfaceComposer.getMaxAcquiredBufferCount().
+prop {
+    api_name: "min_acquired_buffers"
+    type: Long
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.min_acquired_buffers"
+}
+
 # When enabled, SurfaceFlinger will attempt to clear the per-layer HAL buffer cache slots for
 # buffers when they are evicted from the app cache by using additional setLayerBuffer commands.
 # Ideally, this behavior would always be enabled to reduce graphics memory consumption. However,
@@ -484,4 +495,3 @@
     access: Readonly
     prop_name: "ro.surface_flinger.clear_slots_with_set_layer_buffer"
 }
-
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 9660ff3..ba88acc 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -97,6 +97,11 @@
     prop_name: "ro.surface_flinger.max_virtual_display_dimension"
   }
   prop {
+    api_name: "min_acquired_buffers"
+    type: Long
+    prop_name: "ro.surface_flinger.min_acquired_buffers"
+  }
+  prop {
     api_name: "present_time_offset_from_vsync_ns"
     type: Long
     prop_name: "ro.surface_flinger.present_time_offset_from_vsync_ns"
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 62b539a..5888a55 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -30,15 +30,16 @@
     test_suites: ["device-tests"],
     srcs: [
         "BootDisplayMode_test.cpp",
+        "Binder_test.cpp",
         "BufferGenerator.cpp",
-        "CommonTypes_test.cpp",
         "Credentials_test.cpp",
         "DereferenceSurfaceControl_test.cpp",
         "DisplayConfigs_test.cpp",
         "DisplayEventReceiver_test.cpp",
         "EffectLayer_test.cpp",
-        "LayerBorder_test.cpp",
+        "HdrSdrRatioOverlay_test.cpp",
         "InvalidHandles_test.cpp",
+        "LayerBorder_test.cpp",
         "LayerCallback_test.cpp",
         "LayerRenderTypeTransaction_test.cpp",
         "LayerState_test.cpp",
@@ -65,6 +66,7 @@
     static_libs: [
         "liblayers_proto",
         "android.hardware.graphics.composer@2.1",
+        "libsurfaceflingerflags",
     ],
     shared_libs: [
         "android.hardware.graphics.common@1.2",
diff --git a/services/surfaceflinger/tests/Binder_test.cpp b/services/surfaceflinger/tests/Binder_test.cpp
new file mode 100644
index 0000000..3152973
--- /dev/null
+++ b/services/surfaceflinger/tests/Binder_test.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <sched.h>
+
+#include <android/gui/ISurfaceComposer.h>
+#include <android/gui/ISurfaceComposerClient.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <gtest/gtest.h>
+#include <gui/ISurfaceComposer.h>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+namespace android::test {
+using namespace com::android::graphics::surfaceflinger;
+
+class BinderTest : public ::testing::Test {
+protected:
+    BinderTest();
+
+    void SetUp() override;
+
+    void getSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+    void getNonAidlSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+    void getClientSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+    void getDisplayEventConnectionSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+
+private:
+    sp<gui::ISurfaceComposer> mISurfaceComposerAidl;
+    sp<ISurfaceComposer> mISurfaceComposer;
+    sp<gui::ISurfaceComposerClient> mISurfaceComposerClient;
+    sp<gui::IDisplayEventConnection> mConnection;
+};
+
+BinderTest::BinderTest() {
+    const String16 name("SurfaceFlingerAIDL");
+    mISurfaceComposerAidl = waitForService<gui::ISurfaceComposer>(String16("SurfaceFlingerAIDL"));
+    mISurfaceComposer = waitForService<ISurfaceComposer>(String16("SurfaceFlinger"));
+    mISurfaceComposerAidl->createConnection(&mISurfaceComposerClient);
+    mISurfaceComposerAidl
+            ->createDisplayEventConnection(gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
+                                           gui::ISurfaceComposer::EventRegistration(0), {},
+                                           &mConnection);
+}
+
+void BinderTest::SetUp() {
+    ASSERT_TRUE(mISurfaceComposerAidl);
+    ASSERT_TRUE(mISurfaceComposer);
+    ASSERT_TRUE(mISurfaceComposerClient);
+    ASSERT_TRUE(mConnection);
+}
+
+void BinderTest::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    const auto status = mISurfaceComposerAidl->getSchedulingPolicy(outPolicy);
+    ASSERT_TRUE(status.isOk());
+}
+
+void BinderTest::getNonAidlSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    Parcel data, reply;
+    const status_t status =
+            IInterface::asBinder(mISurfaceComposer)
+                    ->transact(BnSurfaceComposer::GET_SCHEDULING_POLICY, data, &reply);
+    ASSERT_EQ(OK, status);
+
+    outPolicy->policy = reply.readInt32();
+    outPolicy->priority = reply.readInt32();
+}
+
+void BinderTest::getClientSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    const auto status = mISurfaceComposerClient->getSchedulingPolicy(outPolicy);
+    ASSERT_TRUE(status.isOk());
+}
+
+void BinderTest::getDisplayEventConnectionSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+    const auto status = mConnection->getSchedulingPolicy(outPolicy);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(BinderTest, SchedulingPolicy) {
+    if (!flags::misc1()) GTEST_SKIP();
+
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTest, NonAidlSchedulingPolicy) {
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getNonAidlSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTest, ClientSchedulingPolicy) {
+    if (!flags::misc1()) GTEST_SKIP();
+
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getClientSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTest, DisplayEventConnectionSchedulingPolicy) {
+    if (!flags::misc1()) GTEST_SKIP();
+
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getDisplayEventConnectionSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+class BinderTestRtCaller : public BinderTest {
+protected:
+    void SetUp() override;
+    void TearDown() override;
+
+private:
+    int mOrigPolicy;
+    int mOrigPriority;
+};
+
+void BinderTestRtCaller::SetUp() {
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    mOrigPolicy = sched_getscheduler(0);
+    struct sched_param origSchedParam;
+    ASSERT_GE(0, sched_getparam(0, &origSchedParam)) << "errno: " << strerror(errno);
+    mOrigPriority = origSchedParam.sched_priority;
+
+    struct sched_param param;
+    param.sched_priority = priority;
+    ASSERT_GE(0, sched_setscheduler(0, policy, &param)) << "errno: " << strerror(errno);
+}
+
+void BinderTestRtCaller::TearDown() {
+    struct sched_param origSchedParam;
+    origSchedParam.sched_priority = mOrigPriority;
+    ASSERT_GE(0, sched_setscheduler(0, mOrigPolicy, &origSchedParam))
+            << "errno: " << strerror(errno);
+}
+
+TEST_F(BinderTestRtCaller, SchedulingPolicy) {
+    if (!flags::misc1()) GTEST_SKIP();
+
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTestRtCaller, NonAidlSchedulingPolicy) {
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getNonAidlSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTestRtCaller, ClientSchedulingPolicy) {
+    if (!flags::misc1()) GTEST_SKIP();
+
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getClientSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTestRtCaller, DisplayEventConnectionSchedulingPolicy) {
+    if (!flags::misc1()) GTEST_SKIP();
+
+    const int policy = SCHED_FIFO;
+    const int priority = sched_get_priority_min(policy);
+
+    gui::SchedulingPolicy sfPolicy;
+    ASSERT_NO_FATAL_FAILURE(getDisplayEventConnectionSchedulingPolicy(&sfPolicy));
+
+    ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+    ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+} // namespace android::test
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
index f2874ae..4f41a81 100644
--- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -28,33 +28,95 @@
 
 using gui::aidl_utils::statusTFromBinderStatus;
 
-TEST(BootDisplayModeTest, setBootDisplayMode) {
-    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+struct BootDisplayModeTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        mSf = ComposerServiceAIDL::getComposerService();
 
-    const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    ASSERT_FALSE(ids.empty());
-    auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-    bool bootModeSupport = false;
-    binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport);
-    ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
-    if (bootModeSupport) {
-        status = sf->setBootDisplayMode(displayToken, 0);
+        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        ASSERT_FALSE(ids.empty());
+        mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+        bool bootModeSupport = false;
+        binder::Status status = mSf->getBootDisplayModeSupport(&bootModeSupport);
+        ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
+
+        if (!bootModeSupport) {
+            GTEST_SKIP() << "Boot mode not supported";
+        }
+
+        gui::DynamicDisplayInfo info;
+        status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
         ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+        mOldMode = info.preferredBootDisplayMode;
+        const auto newMode = [&]() -> std::optional<ui::DisplayModeId> {
+            for (const auto& mode : info.supportedDisplayModes) {
+                if (mode.id != mOldMode) {
+                    return std::optional(mode.id);
+                }
+            }
+            return std::nullopt;
+        }();
+
+        if (!newMode) {
+            GTEST_SKIP() << "Only a single mode is supported";
+        }
+
+        mNewMode = *newMode;
     }
+
+    void TearDown() override {
+        binder::Status status = mSf->setBootDisplayMode(mDisplayToken, mOldMode);
+        EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+        gui::DynamicDisplayInfo info;
+        status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+        EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+        EXPECT_EQ(mOldMode, info.preferredBootDisplayMode);
+    }
+
+    ui::DisplayModeId mOldMode;
+    ui::DisplayModeId mNewMode;
+    sp<gui::ISurfaceComposer> mSf;
+    sp<IBinder> mDisplayToken;
+};
+
+TEST_F(BootDisplayModeTest, setBootDisplayMode) {
+    // Set a new mode and check that it got applied
+    binder::Status status = mSf->setBootDisplayMode(mDisplayToken, mNewMode);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    gui::DynamicDisplayInfo info;
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    EXPECT_EQ(mNewMode, info.preferredBootDisplayMode);
 }
 
-TEST(BootDisplayModeTest, clearBootDisplayMode) {
-    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-    const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    ASSERT_FALSE(ids.empty());
-    auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-    bool bootModeSupport = false;
-    binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport);
-    ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
-    if (bootModeSupport) {
-        status = sf->clearBootDisplayMode(displayToken);
-        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
-    }
+TEST_F(BootDisplayModeTest, clearBootDisplayMode) {
+    // Clear once to figure out what the system default is
+    binder::Status status = mSf->clearBootDisplayMode(mDisplayToken);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    gui::DynamicDisplayInfo info;
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    const ui::DisplayModeId systemMode = info.preferredBootDisplayMode;
+    const ui::DisplayModeId newMode = systemMode == mOldMode ? mNewMode : mOldMode;
+
+    // Now set a new mode and clear the boot mode again to figure out if the api worked.
+    status = mSf->setBootDisplayMode(mDisplayToken, newMode);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    EXPECT_EQ(newMode, info.preferredBootDisplayMode);
+
+    status = mSf->clearBootDisplayMode(mDisplayToken);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    EXPECT_EQ(systemMode, info.preferredBootDisplayMode);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/CommonTypes_test.cpp b/services/surfaceflinger/tests/CommonTypes_test.cpp
deleted file mode 100644
index 25b4615..0000000
--- a/services/surfaceflinger/tests/CommonTypes_test.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <aidl/android/hardware/graphics/common/BlendMode.h>
-#include <aidl/android/hardware/graphics/common/Dataspace.h>
-
-#include <android/data_space.h>
-#include <android/hardware/graphics/common/1.2/types.h>
-#include <android/hardware/graphics/composer/2.1/IComposerClient.h>
-
-using AidlBlendMode = aidl::android::hardware::graphics::common::BlendMode;
-using AidlDataspace = aidl::android::hardware::graphics::common::Dataspace;
-
-using HidlBlendMode = android::hardware::graphics::composer::V2_1::IComposerClient::BlendMode;
-using HidlDataspace = android::hardware::graphics::common::V1_2::Dataspace;
-
-static_assert(static_cast<uint32_t>(AidlBlendMode::INVALID) ==
-              static_cast<uint32_t>(HidlBlendMode::INVALID));
-static_assert(static_cast<uint32_t>(AidlBlendMode::NONE) ==
-              static_cast<uint32_t>(HidlBlendMode::NONE));
-static_assert(static_cast<uint32_t>(AidlBlendMode::PREMULTIPLIED) ==
-              static_cast<uint32_t>(HidlBlendMode::PREMULTIPLIED));
-static_assert(static_cast<uint32_t>(AidlBlendMode::COVERAGE) ==
-              static_cast<uint32_t>(HidlBlendMode::COVERAGE));
-
-static_assert(static_cast<uint32_t>(ADATASPACE_UNKNOWN) ==
-              static_cast<uint32_t>(AidlDataspace::UNKNOWN));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB_LINEAR) ==
-              static_cast<uint32_t>(AidlDataspace::SCRGB_LINEAR));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB) == static_cast<uint32_t>(AidlDataspace::SRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB) ==
-              static_cast<uint32_t>(AidlDataspace::SCRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_DISPLAY_P3) ==
-              static_cast<uint32_t>(AidlDataspace::DISPLAY_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020_PQ) ==
-              static_cast<uint32_t>(AidlDataspace::BT2020_PQ));
-static_assert(static_cast<uint32_t>(ADATASPACE_ADOBE_RGB) ==
-              static_cast<uint32_t>(AidlDataspace::ADOBE_RGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020) ==
-              static_cast<uint32_t>(AidlDataspace::BT2020));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT709) ==
-              static_cast<uint32_t>(AidlDataspace::BT709));
-static_assert(static_cast<uint32_t>(ADATASPACE_DCI_P3) ==
-              static_cast<uint32_t>(AidlDataspace::DCI_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB_LINEAR) ==
-              static_cast<uint32_t>(AidlDataspace::SRGB_LINEAR));
-
-static_assert(static_cast<uint32_t>(ADATASPACE_UNKNOWN) ==
-              static_cast<uint32_t>(HidlDataspace::UNKNOWN));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SCRGB_LINEAR));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SCRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_DISPLAY_P3) ==
-              static_cast<uint32_t>(HidlDataspace::DISPLAY_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020_PQ) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_PQ));
-static_assert(static_cast<uint32_t>(ADATASPACE_ADOBE_RGB) ==
-              static_cast<uint32_t>(HidlDataspace::ADOBE_RGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT709) ==
-              static_cast<uint32_t>(HidlDataspace::V0_BT709));
-static_assert(static_cast<uint32_t>(ADATASPACE_DCI_P3) ==
-              static_cast<uint32_t>(HidlDataspace::DCI_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SRGB_LINEAR));
-
-static_assert(static_cast<uint32_t>(AidlDataspace::UNKNOWN) ==
-              static_cast<uint32_t>(HidlDataspace::UNKNOWN));
-static_assert(static_cast<uint32_t>(AidlDataspace::ARBITRARY) ==
-              static_cast<uint32_t>(HidlDataspace::ARBITRARY));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_SHIFT) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_SHIFT));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_MASK) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_MASK));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_UNSPECIFIED) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_UNSPECIFIED));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT709) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT709));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_625) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_625));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_625_UNADJUSTED) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_625_UNADJUSTED));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_525) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_525));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_525_UNADJUSTED) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_525_UNADJUSTED));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT2020) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT2020));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT2020_CONSTANT_LUMINANCE) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT2020_CONSTANT_LUMINANCE));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT470M) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_BT470M));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_FILM) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_FILM));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_DCI_P3) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_DCI_P3));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_ADOBE_RGB) ==
-              static_cast<uint32_t>(HidlDataspace::STANDARD_ADOBE_RGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SHIFT) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_SHIFT));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_MASK) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_MASK));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_UNSPECIFIED) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_UNSPECIFIED));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SRGB) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_SRGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SMPTE_170M) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_SMPTE_170M));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_2) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_2));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_6) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_6));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_8) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_8));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_ST2084) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_ST2084));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_HLG) ==
-              static_cast<uint32_t>(HidlDataspace::TRANSFER_HLG));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_SHIFT) ==
-              static_cast<uint32_t>(HidlDataspace::RANGE_SHIFT));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_MASK) ==
-              static_cast<uint32_t>(HidlDataspace::RANGE_MASK));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_UNSPECIFIED) ==
-              static_cast<uint32_t>(HidlDataspace::RANGE_UNSPECIFIED));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_FULL) ==
-              static_cast<uint32_t>(HidlDataspace::RANGE_FULL));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_LIMITED) ==
-              static_cast<uint32_t>(HidlDataspace::RANGE_LIMITED));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_EXTENDED) ==
-              static_cast<uint32_t>(HidlDataspace::RANGE_EXTENDED));
-static_assert(static_cast<uint32_t>(AidlDataspace::SRGB_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SRGB_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::SCRGB_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SCRGB_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::SRGB) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SRGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::SCRGB) ==
-              static_cast<uint32_t>(HidlDataspace::V0_SCRGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::JFIF) ==
-              static_cast<uint32_t>(HidlDataspace::V0_JFIF));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT601_625) ==
-              static_cast<uint32_t>(HidlDataspace::V0_BT601_625));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT601_525) ==
-              static_cast<uint32_t>(HidlDataspace::V0_BT601_525));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT709) ==
-              static_cast<uint32_t>(HidlDataspace::V0_BT709));
-static_assert(static_cast<uint32_t>(AidlDataspace::DCI_P3_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::DCI_P3_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::DCI_P3) ==
-              static_cast<uint32_t>(HidlDataspace::DCI_P3));
-static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_P3_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::DISPLAY_P3_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_P3) ==
-              static_cast<uint32_t>(HidlDataspace::DISPLAY_P3));
-static_assert(static_cast<uint32_t>(AidlDataspace::ADOBE_RGB) ==
-              static_cast<uint32_t>(HidlDataspace::ADOBE_RGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_LINEAR) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_PQ) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_PQ));
-static_assert(static_cast<uint32_t>(AidlDataspace::DEPTH) ==
-              static_cast<uint32_t>(HidlDataspace::DEPTH));
-static_assert(static_cast<uint32_t>(AidlDataspace::SENSOR) ==
-              static_cast<uint32_t>(HidlDataspace::SENSOR));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_ITU));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU_PQ) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_ITU_PQ));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU_HLG) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_ITU_HLG));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_HLG) ==
-              static_cast<uint32_t>(HidlDataspace::BT2020_HLG));
-static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_BT2020) ==
-              static_cast<uint32_t>(HidlDataspace::DISPLAY_BT2020));
-static_assert(static_cast<uint32_t>(AidlDataspace::DYNAMIC_DEPTH) ==
-              static_cast<uint32_t>(HidlDataspace::DYNAMIC_DEPTH));
-static_assert(static_cast<uint32_t>(AidlDataspace::JPEG_APP_SEGMENTS) ==
-              static_cast<uint32_t>(HidlDataspace::JPEG_APP_SEGMENTS));
-static_assert(static_cast<uint32_t>(AidlDataspace::HEIF) ==
-              static_cast<uint32_t>(HidlDataspace::HEIF));
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 69e9a16..822ac4d 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -275,18 +275,6 @@
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false));
 }
 
-TEST_F(CredentialsTest, CaptureTest) {
-    const auto display = getFirstDisplayToken();
-    std::function<status_t()> condition = [=]() {
-        sp<GraphicBuffer> outBuffer;
-        DisplayCaptureArgs captureArgs;
-        captureArgs.displayToken = display;
-        ScreenCaptureResults captureResults;
-        return ScreenCapture::captureDisplay(captureArgs, captureResults);
-    };
-    ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
-}
-
 TEST_F(CredentialsTest, CaptureLayersTest) {
     setupBackgroundSurface();
     sp<GraphicBuffer> outBuffer;
diff --git a/services/surfaceflinger/tests/EffectLayer_test.cpp b/services/surfaceflinger/tests/EffectLayer_test.cpp
index 52aa502..92ebb8d 100644
--- a/services/surfaceflinger/tests/EffectLayer_test.cpp
+++ b/services/surfaceflinger/tests/EffectLayer_test.cpp
@@ -120,7 +120,7 @@
     const auto canvasSize = 256;
 
     sp<SurfaceControl> leftLayer = createColorLayer("Left", Color::BLUE);
-    sp<SurfaceControl> rightLayer = createColorLayer("Right", Color::GREEN);
+    sp<SurfaceControl> rightLayer = createColorLayer("Right", Color::RED);
     sp<SurfaceControl> blurLayer;
     const auto leftRect = Rect(0, 0, canvasSize / 2, canvasSize);
     const auto rightRect = Rect(canvasSize / 2, 0, canvasSize, canvasSize);
@@ -140,12 +140,12 @@
     {
         auto shot = screenshot();
         shot->expectColor(leftRect, Color::BLUE);
-        shot->expectColor(rightRect, Color::GREEN);
+        shot->expectColor(rightRect, Color::RED);
     }
 
     ASSERT_NO_FATAL_FAILURE(blurLayer = createColorLayer("BackgroundBlur", Color::TRANSPARENT));
 
-    const auto blurRadius = canvasSize / 2;
+    const auto blurRadius = canvasSize / 4;
     asTransaction([&](Transaction& t) {
         t.setLayer(blurLayer, mLayerZBase + 3);
         t.reparent(blurLayer, mParentLayer);
@@ -159,21 +159,26 @@
         auto shot = screenshot();
 
         const auto stepSize = 1;
-        const auto blurAreaOffset = blurRadius * 0.7f;
-        const auto blurAreaStartX = canvasSize / 2 - blurRadius + blurAreaOffset;
-        const auto blurAreaEndX = canvasSize / 2 + blurRadius - blurAreaOffset;
+        const auto expectedBlurAreaSize = blurRadius * 1.5f;
+        const auto blurAreaStartX = canvasSize / 2 - expectedBlurAreaSize / 2;
+        const auto blurAreaEndX = canvasSize / 2 + expectedBlurAreaSize / 2;
+        // testAreaEndY is needed because the setBackgroundBlurRadius API blurs everything behind
+        // the surface, which means it samples pixels from outside the canvasSize and we get some
+        // unexpected colors in the screenshot.
+        const auto testAreaEndY = canvasSize - blurRadius * 2;
+
         Color previousColor;
         Color currentColor;
-        for (int y = 0; y < canvasSize; y++) {
+        for (int y = 0; y < testAreaEndY; y++) {
             shot->checkPixel(0, y, /* r = */ 0, /* g = */ 0, /* b = */ 255);
             previousColor = shot->getPixelColor(0, y);
             for (int x = blurAreaStartX; x < blurAreaEndX; x += stepSize) {
                 currentColor = shot->getPixelColor(x, y);
-                ASSERT_GT(currentColor.g, previousColor.g);
+                ASSERT_GT(currentColor.r, previousColor.r);
                 ASSERT_LT(currentColor.b, previousColor.b);
-                ASSERT_EQ(0, currentColor.r);
+                ASSERT_EQ(0, currentColor.g);
             }
-            shot->checkPixel(canvasSize - 1, y, 0, 255, 0);
+            shot->checkPixel(canvasSize - 1, y, 255, 0, 0);
         }
     }
 }
diff --git a/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
new file mode 100644
index 0000000..77a8f9c
--- /dev/null
+++ b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <private/gui/ComposerService.h>
+#include <chrono>
+
+using ::std::literals::chrono_literals::operator""s;
+
+static constexpr int kHdrSdrRatioOverlayCode = 1043;
+static constexpr int kHdrSdrRatioOverlayEnable = 1;
+static constexpr int kHdrSdrRatioOverlayDisable = 0;
+static constexpr int kHdrSdrRatioOverlayQuery = 2;
+
+// These values must match the ones we used for developer options in
+// com.android.settings.development.ShowHdrSdrRatioPreferenceController
+static_assert(kHdrSdrRatioOverlayCode == 1043);
+static_assert(kHdrSdrRatioOverlayEnable == 1);
+static_assert(kHdrSdrRatioOverlayDisable == 0);
+static_assert(kHdrSdrRatioOverlayQuery == 2);
+
+namespace android {
+
+namespace {
+void sendCommandToSf(int command, Parcel& reply) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    Parcel request;
+    request.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+    request.writeInt32(command);
+    ASSERT_EQ(NO_ERROR,
+              IInterface::asBinder(sf)->transact(kHdrSdrRatioOverlayCode, request, &reply));
+}
+
+bool isOverlayEnabled() {
+    Parcel reply;
+    sendCommandToSf(kHdrSdrRatioOverlayQuery, reply);
+    return reply.readBool();
+}
+
+void waitForOverlay(bool enabled) {
+    static constexpr auto kTimeout = std::chrono::nanoseconds(1s);
+    static constexpr auto kIterations = 10;
+    for (int i = 0; i < kIterations; i++) {
+        if (enabled == isOverlayEnabled()) {
+            return;
+        }
+        std::this_thread::sleep_for(kTimeout / kIterations);
+    }
+}
+
+void toggleOverlay(bool enabled) {
+    if (enabled == isOverlayEnabled()) {
+        return;
+    }
+
+    Parcel reply;
+    const auto command = enabled ? kHdrSdrRatioOverlayEnable : kHdrSdrRatioOverlayDisable;
+    sendCommandToSf(command, reply);
+    waitForOverlay(enabled);
+    ASSERT_EQ(enabled, isOverlayEnabled());
+}
+
+} // namespace
+
+TEST(HdrSdrRatioOverlayTest, enableAndDisableOverlay) {
+    toggleOverlay(true);
+    toggleOverlay(false);
+
+    toggleOverlay(true);
+    toggleOverlay(false);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/IPC_test.cpp b/services/surfaceflinger/tests/IPC_test.cpp
index 40a5d57..18bd3b9 100644
--- a/services/surfaceflinger/tests/IPC_test.cpp
+++ b/services/surfaceflinger/tests/IPC_test.cpp
@@ -289,7 +289,7 @@
             IPCThreadState::self()->joinThreadPool();
             [&]() { exit(0); }();
         }
-        sp<IBinder> binder = defaultServiceManager()->getService(serviceName);
+        sp<IBinder> binder = defaultServiceManager()->waitForService(serviceName);
         remote = interface_cast<IIPCTest>(binder);
         remote->setDeathToken(mDeathRecipient);
     }
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index b8068f7..2b1834d 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -1479,15 +1479,11 @@
     matrix[2][2] = 0.11;
 
     // degamma before applying the matrix
-    if (mColorManagementUsed) {
-        ColorTransformHelper::DegammaColor(expected);
-    }
+    ColorTransformHelper::DegammaColor(expected);
 
     ColorTransformHelper::applyMatrix(expected, matrix);
 
-    if (mColorManagementUsed) {
-        ColorTransformHelper::GammaColor(expected);
-    }
+    ColorTransformHelper::GammaColor(expected);
 
     const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
                                  uint8_t(expected.b * 255), 255};
@@ -1537,15 +1533,11 @@
     matrix[2][2] = 0.11;
 
     // degamma before applying the matrix
-    if (mColorManagementUsed) {
-        ColorTransformHelper::DegammaColor(expected);
-    }
+    ColorTransformHelper::DegammaColor(expected);
 
     ColorTransformHelper::applyMatrix(expected, matrix);
 
-    if (mColorManagementUsed) {
-        ColorTransformHelper::GammaColor(expected);
-    }
+    ColorTransformHelper::GammaColor(expected);
 
     const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
                                  uint8_t(expected.b * 255), 255};
@@ -1608,16 +1600,12 @@
     matrixParent[2][2] = 0.10;
 
     // degamma before applying the matrix
-    if (mColorManagementUsed) {
-        ColorTransformHelper::DegammaColor(expected);
-    }
+    ColorTransformHelper::DegammaColor(expected);
 
     ColorTransformHelper::applyMatrix(expected, matrixChild);
     ColorTransformHelper::applyMatrix(expected, matrixParent);
 
-    if (mColorManagementUsed) {
-        ColorTransformHelper::GammaColor(expected);
-    }
+    ColorTransformHelper::GammaColor(expected);
 
     const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
                                  uint8_t(expected.b * 255), 255};
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index badd5be..2bdb8a4 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -47,9 +47,6 @@
         ASSERT_NO_FATAL_FAILURE(SetUpDisplay());
 
         sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-        binder::Status status = sf->getColorManagement(&mColorManagementUsed);
-        ASSERT_NO_FATAL_FAILURE(gui::aidl_utils::statusTFromBinderStatus(status));
-
         mCaptureArgs.displayToken = mDisplay;
     }
 
@@ -282,7 +279,6 @@
     const int32_t mLayerZBase = std::numeric_limits<int32_t>::max() - 256;
 
     sp<SurfaceControl> mBlackBgSurface;
-    bool mColorManagementUsed;
 
     DisplayCaptureArgs mCaptureArgs;
     ScreenCaptureResults mCaptureResults;
diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp
index cbd54e7..03de8d0 100644
--- a/services/surfaceflinger/tests/LayerTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTransaction_test.cpp
@@ -184,6 +184,35 @@
     }
 }
 
+TEST_F(LayerTransactionTest, CommitCallbackCalledOnce) {
+    auto callCount = 0;
+    auto commitCallback =
+            [&callCount](void* /* context */, nsecs_t /* latchTime */,
+                         const sp<Fence>& /* presentFence */,
+                         const std::vector<SurfaceControlStats>& /* stats */) mutable {
+                callCount++;
+            };
+
+    // Create two transactions that both contain the same callback id.
+    Transaction t1;
+    t1.addTransactionCommittedCallback(commitCallback, nullptr);
+    Parcel parcel;
+    t1.writeToParcel(&parcel);
+    parcel.setDataPosition(0);
+    Transaction t2;
+    t2.readFromParcel(&parcel);
+
+    // Apply the two transactions. There is a race here as we can't guarantee that the two
+    // transactions will be applied within the same SurfaceFlinger commit. If the transactions are
+    // applied within the same commit then we verify that callback ids are deduplicated within a
+    // single commit. Otherwise, we verify that commit callbacks are deduplicated across separate
+    // commits.
+    t1.apply();
+    t2.apply(/*synchronous=*/true);
+
+    ASSERT_EQ(callCount, 1);
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
index 34c9182..f9b4bba 100644
--- a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
@@ -167,18 +167,18 @@
             .setFlags(layer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
             .apply(true);
 
-    DisplayCaptureArgs args;
-    args.displayToken = mDisplay;
+    LayerCaptureArgs args;
+    args.layerHandle = layer->getHandle();
 
     ScreenCaptureResults captureResults;
     {
         // Ensure the UID is not root because root has all permissions
         UIDFaker f(AID_APP_START);
-        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(args, captureResults));
+        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(args, captureResults));
     }
 
     Transaction().setFlags(layer, 0, layer_state_t::eLayerSecure).apply(true);
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, captureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(args, captureResults));
 }
 
 TEST_P(LayerTypeTransactionTest, RefreshRateIsInitialized) {
diff --git a/services/surfaceflinger/tests/Stress_test.cpp b/services/surfaceflinger/tests/Stress_test.cpp
index 03201f7..b30df5e 100644
--- a/services/surfaceflinger/tests/Stress_test.cpp
+++ b/services/surfaceflinger/tests/Stress_test.cpp
@@ -51,9 +51,9 @@
     }
 }
 
-surfaceflinger::LayersProto generateLayerProto() {
-    surfaceflinger::LayersProto layersProto;
-    std::array<surfaceflinger::LayerProto*, 10> layers = {};
+perfetto::protos::LayersProto generateLayerProto() {
+    perfetto::protos::LayersProto layersProto;
+    std::array<perfetto::protos::LayerProto*, 10> layers = {};
     for (size_t i = 0; i < layers.size(); ++i) {
         layers[i] = layersProto.add_layers();
         layers[i]->set_id(i);
@@ -103,7 +103,7 @@
     cmd += std::to_string(getpid());
     system(cmd.c_str());
     for (int i = 0; i < 100000; i++) {
-        surfaceflinger::LayersProto layersProto = generateLayerProto();
+        perfetto::protos::LayersProto layersProto = generateLayerProto();
         auto layerTree = surfaceflinger::LayerProtoParser::generateLayerTree(layersProto);
         surfaceflinger::LayerProtoParser::layerTreeToString(layerTree);
     }
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index 21ebaea..aeceadb 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -29,13 +29,9 @@
         "skia_renderengine_deps",
     ],
     test_suites: ["device-tests"],
-    sanitize: {
-        address: true,
-    },
     srcs: [
         ":libsurfaceflinger_sources",
         ":libsurfaceflinger_mock_sources",
-        ":layertracegenerator_sources",
         "TransactionTraceTestSuite.cpp",
     ],
     static_libs: [
@@ -43,7 +39,6 @@
     ],
     header_libs: [
         "libsurfaceflinger_mocks_headers",
-        "layertracegenerator_headers",
     ],
     data: ["testdata/*"],
 }
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 9526948..3c09422 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -23,8 +23,9 @@
 #include <unordered_map>
 
 #include <LayerProtoHelper.h>
-#include <LayerTraceGenerator.h>
+#include <Tracing/LayerTracing.h>
 #include <Tracing/TransactionProtoParser.h>
+#include <Tracing/tools/LayerTraceGenerator.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <log/log.h>
 
@@ -40,9 +41,9 @@
     static constexpr std::string_view sLayersTracePrefix = "layers_trace_";
     static constexpr std::string_view sTracePostfix = ".winscope";
 
-    proto::TransactionTraceFile mTransactionTrace;
-    LayersTraceFileProto mExpectedLayersTraceProto;
-    LayersTraceFileProto mActualLayersTraceProto;
+    perfetto::protos::TransactionTraceFile mTransactionTrace;
+    perfetto::protos::LayersTraceFileProto mExpectedLayersTraceProto;
+    perfetto::protos::LayersTraceFileProto mActualLayersTraceProto;
 
 protected:
     void SetUp() override {
@@ -56,18 +57,24 @@
         EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(expectedLayersTracePath)));
         parseLayersTraceFromFile(expectedLayersTracePath.c_str(), mExpectedLayersTraceProto);
         TemporaryDir temp_dir;
+
         std::string actualLayersTracePath =
                 std::string(temp_dir.path) + "/" + expectedLayersFilename + "_actual";
+        {
+            auto traceFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS;
+            std::ofstream outStream{actualLayersTracePath, std::ios::binary | std::ios::app};
+            auto layerTracing = LayerTracing{outStream};
+            EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, traceFlags, layerTracing,
+                                                       /*onlyLastEntry=*/true))
+                    << "Failed to generate layers trace from " << transactionTracePath;
+        }
 
-        EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str(),
-                                                   /*onlyLastEntry=*/true))
-                << "Failed to generate layers trace from " << transactionTracePath;
         EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(actualLayersTracePath)));
         parseLayersTraceFromFile(actualLayersTracePath.c_str(), mActualLayersTraceProto);
     }
 
     void parseTransactionTraceFromFile(const char* transactionTracePath,
-                                       proto::TransactionTraceFile& outProto) {
+                                       perfetto::protos::TransactionTraceFile& outProto) {
         ALOGD("Parsing file %s...", transactionTracePath);
         std::fstream input(transactionTracePath, std::ios::in | std::ios::binary);
         EXPECT_TRUE(input) << "Error could not open " << transactionTracePath;
@@ -75,7 +82,8 @@
                 << "Failed to parse " << transactionTracePath;
     }
 
-    void parseLayersTraceFromFile(const char* layersTracePath, LayersTraceFileProto& outProto) {
+    void parseLayersTraceFromFile(const char* layersTracePath,
+                                  perfetto::protos::LayersTraceFileProto& outProto) {
         ALOGD("Parsing file %s...", layersTracePath);
         std::fstream input(layersTracePath, std::ios::in | std::ios::binary);
         EXPECT_TRUE(input) << "Error could not open " << layersTracePath;
@@ -124,7 +132,7 @@
     bool operator()(LayerInfo const& m) const { return m.id == id; }
 };
 
-static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& proto) {
+static LayerInfo getLayerInfoFromProto(perfetto::protos::LayerProto& proto) {
     Rect touchableRegionBounds = Rect::INVALID_RECT;
     // ignore touchable region for layers without buffers, the new fe aggressively avoids
     // calculating state for layers that are not visible which could lead to mismatches
@@ -148,8 +156,7 @@
             touchableRegionBounds};
 }
 
-static std::vector<LayerInfo> getLayerInfosFromProto(
-        android::surfaceflinger::LayersTraceProto& entry) {
+static std::vector<LayerInfo> getLayerInfosFromProto(perfetto::protos::LayersSnapshotProto& entry) {
     std::unordered_map<uint64_t /* snapshotId*/, uint64_t /*layerId*/> snapshotIdToLayerId;
     std::vector<LayerInfo> layers;
     layers.reserve(static_cast<size_t>(entry.layers().layers_size()));
@@ -267,4 +274,4 @@
     }
     ::testing::InitGoogleTest(&argc, argv);
     return RUN_ALL_TESTS();
-}
\ No newline at end of file
+}
diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
index 7077523..f1bb231 100644
--- a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -85,7 +85,7 @@
     ASSERT_EQ(ui::Transform::ROT_90, SurfaceFlinger::getActiveDisplayRotationFlags());
 }
 
-TEST_F(ActiveDisplayRotationFlagsTest, rotate90_inactive) {
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90inactive) {
     auto displayToken = mOuterDisplay->getDisplayToken().promote();
     mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
     mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
@@ -95,7 +95,7 @@
     ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
 }
 
-TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_innerActive) {
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBothInnerActive) {
     auto displayToken = mInnerDisplay->getDisplayToken().promote();
     mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
     mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
@@ -110,7 +110,7 @@
     ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
 }
 
-TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_outerActive) {
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBothOuterActive) {
     mFlinger.mutableActiveDisplayId() = kOuterDisplayId;
     auto displayToken = mInnerDisplay->getDisplayToken().promote();
     mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index ceb69df..5a3bca1 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -42,6 +42,12 @@
     ],
 }
 
+cc_aconfig_library {
+    name: "libsurfaceflingerflags_test",
+    aconfig_declarations: "surfaceflinger_flags",
+    test: true,
+}
+
 cc_test {
     name: "libsurfaceflinger_unittest",
     defaults: [
@@ -50,28 +56,14 @@
         "surfaceflinger_defaults",
     ],
     test_suites: ["device-tests"],
-    sanitize: {
-        // Using the address sanitizer not only helps uncover issues in the code
-        // covered by the tests, but also covers some of the tricky injection of
-        // fakes the unit tests currently do.
-        //
-        // Note: If you get an runtime link error like:
-        //
-        //   CANNOT LINK EXECUTABLE "/data/local/tmp/libsurfaceflinger_unittest": library "libclang_rt.asan-aarch64-android.so" not found
-        //
-        // it is because the address sanitizer shared objects are not installed
-        // by default in the system image.
-        //
-        // You can either "make dist tests" before flashing, or set this
-        // option to false temporarily.
-        address: true,
-    },
+    static_libs: ["libc++fs"],
     srcs: [
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
         "ActiveDisplayRotationFlagsTest.cpp",
         "BackgroundExecutorTest.cpp",
+        "CommitTest.cpp",
         "CompositionTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
@@ -86,11 +78,13 @@
         "FramebufferSurfaceTest.cpp",
         "FrameRateOverrideMappingsTest.cpp",
         "FrameRateSelectionPriorityTest.cpp",
+        "FrameRateSelectionStrategyTest.cpp",
         "FrameTimelineTest.cpp",
         "GameModeTest.cpp",
         "HWComposerTest.cpp",
         "OneShotTimerTest.cpp",
         "LayerHistoryTest.cpp",
+        "LayerHistoryIntegrationTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
         "LayerHierarchyTest.cpp",
@@ -129,6 +123,7 @@
         "TransactionFrameTracerTest.cpp",
         "TransactionProtoParserTest.cpp",
         "TransactionSurfaceFrameTest.cpp",
+        "TransactionTraceWriterTest.cpp",
         "TransactionTracingTest.cpp",
         "TunnelModeEnabledReporterTest.cpp",
         "StrongTypingTest.cpp",
@@ -149,6 +144,7 @@
     defaults: [
         "android.hardware.graphics.common-ndk_static",
         "android.hardware.graphics.composer3-ndk_static",
+        "android.hardware.power-ndk_static",
         "librenderengine_deps",
     ],
     static_libs: [
@@ -162,7 +158,6 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
         "libaidlcommonsupport",
         "libcompositionengine_mocks",
         "libcompositionengine",
@@ -180,6 +175,7 @@
         "libtimestats_proto",
         "libtonemap",
         "perfetto_trace_protos",
+        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         "android.hardware.configstore-utils",
diff --git a/services/surfaceflinger/tests/unittests/CommitTest.cpp b/services/surfaceflinger/tests/unittests/CommitTest.cpp
new file mode 100644
index 0000000..df53d19
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/CommitTest.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CommitTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <renderengine/mock/RenderEngine.h>
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class CommitTest : public testing::Test {
+protected:
+    CommitTest() {
+        mFlinger.setupMockScheduler();
+        mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    }
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+};
+
+namespace {
+
+TEST_F(CommitTest, noUpdatesDoesNotScheduleComposite) {
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/0, unused);
+    EXPECT_FALSE(mustComposite);
+}
+
+// Ensure that we handle eTransactionNeeded correctly
+TEST_F(CommitTest, eTransactionNeededFlagSchedulesComposite) {
+    // update display level color matrix
+    mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/0, unused);
+    EXPECT_TRUE(mustComposite);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index e8a9cfe..8e13c0d 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -31,8 +31,6 @@
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
 #include <renderengine/mock/FakeExternalTexture.h>
-#include <renderengine/mock/Framebuffer.h>
-#include <renderengine/mock/Image.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <system/window.h>
 #include <utils/String8.h>
@@ -330,7 +328,7 @@
                 .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    const bool, base::unique_fd&&) -> std::future<FenceResult> {
+                                    base::unique_fd&&) -> std::future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -381,7 +379,7 @@
                 .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    const bool, base::unique_fd&&) -> std::future<FenceResult> {
+                                    base::unique_fd&&) -> std::future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -580,7 +578,7 @@
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
-                              const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                              const std::shared_ptr<renderengine::ExternalTexture>&,
                               base::unique_fd&&) -> std::future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
@@ -599,8 +597,6 @@
                     const renderengine::LayerSettings layer = layerSettings.back();
                     EXPECT_THAT(layer.source.buffer.buffer, Not(IsNull()));
                     EXPECT_THAT(layer.source.buffer.fence, Not(IsNull()));
-                    EXPECT_EQ(DEFAULT_TEXTURE_ID, layer.source.buffer.textureName);
-                    EXPECT_EQ(false, layer.source.buffer.isY410BT2020);
                     EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha);
                     EXPECT_EQ(false, layer.source.buffer.isOpaque);
                     EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
@@ -631,7 +627,7 @@
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
-                              const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                              const std::shared_ptr<renderengine::ExternalTexture>&,
                               base::unique_fd&&) -> std::future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
@@ -713,7 +709,7 @@
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
-                              const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                              const std::shared_ptr<renderengine::ExternalTexture>&,
                               base::unique_fd&&) -> std::future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
@@ -876,15 +872,11 @@
     using FlingerLayerType = sp<Layer>;
 
     static FlingerLayerType createLayer(CompositionTest* test) {
-        test->mFlinger.mutableTexturePool().push_back(DEFAULT_TEXTURE_ID);
-
-        FlingerLayerType layer =
-                Base::template createLayerWithFactory<Layer>(test, [test]() {
-                    LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
-                                           LayerProperties::LAYER_FLAGS, LayerMetadata());
-                    args.textureName = test->mFlinger.mutableTexturePool().back();
-                    return sp<Layer>::make(args);
-                });
+        FlingerLayerType layer = Base::template createLayerWithFactory<Layer>(test, [test]() {
+            LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
+                                   LayerProperties::LAYER_FLAGS, LayerMetadata());
+            return sp<Layer>::make(args);
+        });
 
         LayerProperties::setupLayerState(test, layer);
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index e32cf88..fa31643 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -67,15 +67,13 @@
 
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(mEventThread,
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+            .WillOnce(Return(
+                    sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
 
     EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
             .WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+                                                             mock::EventThread::kCallingUid)));
 
     mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
                             std::make_shared<mock::VSyncTracker>(),
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 5fed9b4..8891c06 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -57,7 +57,7 @@
 
 } // namespace
 
-class EventThreadTest : public testing::Test {
+class EventThreadTest : public testing::Test, public IEventThreadCallback {
 protected:
     static constexpr std::chrono::nanoseconds kWorkDuration = 0ms;
     static constexpr std::chrono::nanoseconds kReadyDuration = 3ms;
@@ -65,10 +65,8 @@
     class MockEventThreadConnection : public EventThreadConnection {
     public:
         MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
-                                  ResyncCallback&& resyncCallback,
                                   EventRegistrationFlags eventRegistration)
-              : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback),
-                                      eventRegistration) {}
+              : EventThreadConnection(eventThread, callingUid, eventRegistration) {}
         MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
     };
 
@@ -78,7 +76,14 @@
     EventThreadTest();
     ~EventThreadTest() override;
 
-    void setupEventThread(std::chrono::nanoseconds vsyncPeriod);
+    void SetUp() override { mVsyncPeriod = VSYNC_PERIOD; }
+
+    // IEventThreadCallback overrides
+    bool throttleVsync(TimePoint, uid_t) override;
+    Period getVsyncPeriod(uid_t) override;
+    void resync() override;
+
+    void setupEventThread();
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
                                                    EventRegistrationFlags eventRegistration = {},
                                                    uid_t ownerUid = mConnectionUid);
@@ -92,8 +97,7 @@
     void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
     void expectVsyncEventFrameTimelinesCorrect(
             nsecs_t expectedTimestamp, gui::VsyncEventData::FrameTimeline preferredVsyncData);
-    void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData,
-                                                       std::chrono::nanoseconds vsyncPeriod);
+    void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData);
     void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                 bool expectedConnected);
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -133,6 +137,8 @@
     sp<MockEventThreadConnection> mThrottledConnection;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
 
+    std::chrono::nanoseconds mVsyncPeriod;
+
     static constexpr uid_t mConnectionUid = 443;
     static constexpr uid_t mThrottledConnectionUid = 177;
 };
@@ -168,17 +174,24 @@
     EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value());
 }
 
-void EventThreadTest::setupEventThread(std::chrono::nanoseconds vsyncPeriod) {
-    const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
-        mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
-        return (uid == mThrottledConnectionUid);
-    };
-    const auto getVsyncPeriod = [vsyncPeriod](uid_t uid) { return vsyncPeriod.count(); };
+bool EventThreadTest::throttleVsync(android::TimePoint expectedVsyncTimestamp, uid_t uid) {
+    mThrottleVsyncCallRecorder.recordCall(expectedVsyncTimestamp.ns(), uid);
+    return (uid == mThrottledConnectionUid);
+}
 
+Period EventThreadTest::getVsyncPeriod(uid_t) {
+    return mVsyncPeriod;
+}
+
+void EventThreadTest::resync() {
+    mResyncCallRecorder.recordCall();
+}
+
+void EventThreadTest::setupEventThread() {
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
     mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
-                                                  mTokenManager.get(), throttleVsync,
-                                                  getVsyncPeriod, kWorkDuration, kReadyDuration);
+                                                  mTokenManager.get(), *this, kWorkDuration,
+                                                  kReadyDuration);
 
     // EventThread should register itself as VSyncSource callback.
     EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
@@ -200,9 +213,7 @@
         ConnectionEventRecorder& recorder, EventRegistrationFlags eventRegistration,
         uid_t ownerUid) {
     sp<MockEventThreadConnection> connection =
-            sp<MockEventThreadConnection>::make(mThread.get(), ownerUid,
-                                                mResyncCallRecorder.getInvocable(),
-                                                eventRegistration);
+            sp<MockEventThreadConnection>::make(mThread.get(), ownerUid, eventRegistration);
     EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
     return connection;
 }
@@ -292,10 +303,9 @@
     }
 }
 
-void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(
-        VsyncEventData vsyncEventData, std::chrono::nanoseconds vsyncPeriod) {
+void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData) {
     float nonPreferredTimelinesAmount =
-            scheduler::VsyncConfig::kEarlyLatchMaxThreshold / vsyncPeriod;
+            scheduler::VsyncConfig::kEarlyLatchMaxThreshold / mVsyncPeriod;
     EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1)
             << "Amount of non-preferred frame timelines too many;"
             << " expected presentation time will be over threshold";
@@ -357,7 +367,7 @@
  */
 
 TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value());
     EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value());
@@ -368,7 +378,7 @@
 }
 
 TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
@@ -381,7 +391,7 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
@@ -414,7 +424,7 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
@@ -428,12 +438,12 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) {
+    setupEventThread();
     // The VsyncEventData should not have kFrameTimelinesCapacity amount of valid frame timelines,
     // due to longer vsync period and kEarlyLatchMaxThreshold. Use length-2 to avoid decimal
     // truncation (e.g. 60Hz has 16.6... ms vsync period).
-    std::chrono::nanoseconds vsyncPeriod(scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
-                                         (VsyncEventData::kFrameTimelinesCapacity - 2));
-    setupEventThread(vsyncPeriod);
+    mVsyncPeriod = (scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
+                    (VsyncEventData::kFrameTimelinesCapacity - 2));
 
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
@@ -449,11 +459,11 @@
     ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
                                   << expectedTimestamp;
     const VsyncEventData vsyncEventData = std::get<0>(args.value()).vsync.vsyncData;
-    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, vsyncPeriod);
+    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
 }
 
 TEST_F(EventThreadTest, getLatestVsyncEventData) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const nsecs_t now = systemTime();
     const nsecs_t preferredExpectedPresentationTime = now + 20000000;
@@ -469,7 +479,7 @@
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
 
-    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, VSYNC_PERIOD);
+    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
     EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
             << "Deadline timestamp should be greater than frame time";
     for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
@@ -508,7 +518,7 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Create a first connection, register it, and request a vsync rate of zero.
     ConnectionEventRecorder firstConnectionEventRecorder{0};
@@ -537,7 +547,7 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setVsyncRate(1, mConnection);
 
@@ -562,7 +572,7 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setVsyncRate(2, mConnection);
 
@@ -590,7 +600,7 @@
 }
 
 TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setVsyncRate(1, mConnection);
 
@@ -609,7 +619,7 @@
 }
 
 TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -634,33 +644,8 @@
     expectVSyncCallbackScheduleReceived(false);
 }
 
-TEST_F(EventThreadTest, tracksEventConnections) {
-    setupEventThread(VSYNC_PERIOD);
-
-    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
-    ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
-    sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
-    mThread->setVsyncRate(1, errorConnection);
-    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
-    ConnectionEventRecorder secondConnectionEventRecorder{0};
-    sp<MockEventThreadConnection> secondConnection =
-            createConnection(secondConnectionEventRecorder);
-    mThread->setVsyncRate(1, secondConnection);
-    EXPECT_EQ(4, mThread->getEventThreadConnectionCount());
-
-    // EventThread should enable vsync callbacks.
-    expectVSyncCallbackScheduleReceived(true);
-
-    // The first event will be seen by the connection, which then returns an error.
-    onVSyncEvent(123, 456, 789);
-    expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
-    expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123,
-                                         1u);
-    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
-}
-
 TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -686,83 +671,83 @@
 }
 
 TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setDuration(321ns, 456ns);
     expectVSyncSetDurationCallReceived(321ns, 456ns);
 }
 
 TEST_F(EventThreadTest, postHotplugInternalDisconnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
 }
 
 TEST_F(EventThreadTest, postHotplugInternalConnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
 }
 
 TEST_F(EventThreadTest, postHotplugExternalDisconnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, false);
 }
 
 TEST_F(EventThreadTest, postHotplugExternalConnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, true);
     expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, true);
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(INTERNAL_DISPLAY_ID)
                               .setId(DisplayModeId(7))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
 
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, fps.getPeriodNsecs());
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(EXTERNAL_DISPLAY_ID)
                               .setId(DisplayModeId(5))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
 
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, fps.getPeriodNsecs());
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(DISPLAY_ID_64BIT)
                               .setId(DisplayModeId(7))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, fps.getPeriodNsecs());
 }
 
 TEST_F(EventThreadTest, suppressConfigChanged) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     ConnectionEventRecorder suppressConnectionEventRecorder{0};
     sp<MockEventThreadConnection> suppressConnection =
@@ -773,7 +758,7 @@
                               .setId(DisplayModeId(9))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
 
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, fps.getPeriodNsecs());
@@ -783,7 +768,7 @@
 }
 
 TEST_F(EventThreadTest, postUidFrameRateMapping) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const std::vector<FrameRateOverride> overrides = {
             {.uid = 1, .frameRateHz = 20},
@@ -796,7 +781,7 @@
 }
 
 TEST_F(EventThreadTest, suppressUidFrameRateMapping) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const std::vector<FrameRateOverride> overrides = {
             {.uid = 1, .frameRateHz = 20},
@@ -816,7 +801,7 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Signal that we want the next vsync event to be posted to the throttled connection
     mThread->requestNextVsync(mThrottledConnection);
diff --git a/services/surfaceflinger/tests/unittests/FlagUtils.h b/services/surfaceflinger/tests/unittests/FlagUtils.h
new file mode 100644
index 0000000..7103684
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FlagUtils.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
+
+namespace android {
+class TestFlagSetter {
+public:
+    TestFlagSetter(bool (*getter)(), void((*setter)(bool)), bool flagValue) {
+        const bool initialValue = getter();
+        setter(flagValue);
+        mResetFlagValue = [=] { setter(initialValue); };
+    }
+
+    ~TestFlagSetter() { mResetFlagValue(); }
+
+private:
+    std::function<void()> mResetFlagValue;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
index 1c9aee7..d30d5b8 100644
--- a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
@@ -99,8 +99,7 @@
 }
 
 void RefreshRateSelectionTest::commitTransaction(Layer* layer) {
-    auto c = layer->getDrawingState();
-    layer->commitTransaction(c);
+    layer->commitTransaction();
 }
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
new file mode 100644
index 0000000..20ea0c0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "Layer.h"
+#include "LayerTestUtils.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+
+namespace android {
+
+using testing::DoAll;
+using testing::Mock;
+using testing::SetArgPointee;
+
+using android::Hwc2::IComposer;
+using android::Hwc2::IComposerClient;
+
+using scheduler::LayerHistory;
+
+using FrameRate = Layer::FrameRate;
+using FrameRateCompatibility = Layer::FrameRateCompatibility;
+using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
+
+/**
+ * This class tests the behaviour of Layer::setFrameRateSelectionStrategy.
+ */
+class FrameRateSelectionStrategyTest : public BaseLayerTest {
+protected:
+    const FrameRate FRAME_RATE_VOTE1 = FrameRate(11_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE2 = FrameRate(22_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE3 = FrameRate(33_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
+
+    FrameRateSelectionStrategyTest();
+
+    void addChild(sp<Layer> layer, sp<Layer> child);
+    void removeChild(sp<Layer> layer, sp<Layer> child);
+    void commitTransaction();
+
+    std::vector<sp<Layer>> mLayers;
+};
+
+FrameRateSelectionStrategyTest::FrameRateSelectionStrategyTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+}
+
+void FrameRateSelectionStrategyTest::addChild(sp<Layer> layer, sp<Layer> child) {
+    layer->addChild(child);
+}
+
+void FrameRateSelectionStrategyTest::removeChild(sp<Layer> layer, sp<Layer> child) {
+    layer->removeChild(child);
+}
+
+void FrameRateSelectionStrategyTest::commitTransaction() {
+    for (auto layer : mLayers) {
+        layer->commitTransaction();
+    }
+}
+
+namespace {
+
+INSTANTIATE_TEST_SUITE_P(PerLayerType, FrameRateSelectionStrategyTest,
+                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
+                                         std::make_shared<EffectLayerFactory>()),
+                         PrintToStringParamName);
+
+TEST_P(FrameRateSelectionStrategyTest, SetAndGet) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    layer->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    commitTransaction();
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetChildAndGetParent) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    addChild(parent, child1);
+    addChild(child1, child2);
+
+    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+    child2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    commitTransaction();
+    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              parent->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              child1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              child2->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetParentAndGet) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    addChild(layer1, layer2);
+    addChild(layer2, layer3);
+
+    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
+    layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    layer3->setFrameRate(FRAME_RATE_VOTE3.vote);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+
+    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+}
+
+} // namespace
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index d26ef3c..636d852 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -54,6 +54,8 @@
 constexpr int32_t sLayerIdOne = 1;
 constexpr int32_t sLayerIdTwo = 2;
 constexpr GameMode sGameMode = GameMode::Unsupported;
+constexpr Fps RR_11 = Fps::fromPeriodNsecs(11);
+constexpr Fps RR_30 = Fps::fromPeriodNsecs(30);
 
 class FrameTimelineTest : public testing::Test {
 public:
@@ -276,7 +278,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
     surfaceFrame1->setDropTime(12);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -309,7 +311,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo,
                                                        sLayerNameTwo, sLayerNameTwo,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -354,7 +356,7 @@
                 mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
-        mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
@@ -379,7 +381,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, RR_11, RR_11);
     surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame);
     mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
@@ -422,7 +424,7 @@
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
-        mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27, presentFence);
@@ -439,7 +441,7 @@
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
-        mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27, presentFence);
@@ -456,7 +458,7 @@
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
-        mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27, presentFence);
@@ -465,7 +467,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
 
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
@@ -478,7 +480,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -496,7 +498,7 @@
 
 // Tests related to TimeStats
 TEST_F(FrameTimelineTest, presentFenceSignaled_doesNotReportForInvalidTokens) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(0);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken1 = -1;
@@ -509,7 +511,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -519,7 +521,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
@@ -537,7 +539,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -547,7 +549,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
@@ -566,7 +568,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -577,7 +579,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
-    Fps refreshRate = Fps::fromPeriodNsecs(30);
+    Fps refreshRate = RR_30;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                                 sLayerNameOne, sGameMode,
@@ -594,7 +596,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setAcquireFenceTime(20);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -622,7 +624,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(45);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -651,7 +653,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(50);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -680,7 +682,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -709,7 +711,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented,
                                    /*previousLatchTime*/ 56);
@@ -721,8 +723,8 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
-    Fps renderRate = Fps::fromPeriodNsecs(30);
+    Fps refreshRate = RR_11;
+    Fps renderRate = RR_30;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne,
                                                                 sLayerNameOne, sGameMode,
@@ -740,7 +742,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(45);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setRenderRate(renderRate);
@@ -752,8 +754,8 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
-    Fps renderRate = Fps::fromPeriodNsecs(30);
+    Fps refreshRate = RR_11;
+    Fps renderRate = RR_30;
 
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
@@ -775,7 +777,7 @@
     surfaceFrame1->setAcquireFenceTime(45);
     // Trigger a prediction expiry
     flushTokens();
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setRenderRate(renderRate);
@@ -814,7 +816,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(25, presentFence1);
@@ -844,7 +846,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token2, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token2, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(25, presentFence1);
@@ -866,7 +868,7 @@
     tracingSession->StartBlocking();
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(-1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(-1, 20, RR_11, RR_11);
     mFrameTimeline->setSfPresent(25, presentFence1);
     presentFence1->signalForTest(30);
 
@@ -890,7 +892,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(25, presentFence1);
@@ -1068,7 +1070,7 @@
     int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
     mFrameTimeline->setSfPresent(26, presentFence1);
     presentFence1->signalForTest(31);
 
@@ -1150,7 +1152,7 @@
     addEmptySurfaceFrame();
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
     mFrameTimeline->setSfPresent(26, presentFence1);
     presentFence1->signalForTest(31);
 
@@ -1198,7 +1200,7 @@
 TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
     auto tracingSession = getTracingSessionForTest();
     // Layer specific increment
-    EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
+    EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
@@ -1234,8 +1236,8 @@
     auto protoDroppedSurfaceFrameActualStart =
             createProtoActualSurfaceFrameStart(traceCookie + 2, surfaceFrameToken,
                                                displayFrameToken1, sPidOne, sLayerNameOne,
-                                               FrameTimelineEvent::PRESENT_DROPPED, false, false,
-                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PRESENT_DROPPED, true, false,
+                                               FrameTimelineEvent::JANK_DROPPED,
                                                FrameTimelineEvent::PREDICTION_VALID, true);
     auto protoDroppedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 2);
 
@@ -1252,7 +1254,7 @@
     auto protoPresentedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 4);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -1398,7 +1400,7 @@
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(sfEndTime, presentFence1);
@@ -1470,12 +1472,12 @@
             createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken,
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_DROPPED, false, false,
-                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::JANK_DROPPED,
                                                FrameTimelineEvent::PREDICTION_EXPIRED, true);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, RR_11, RR_11);
     surfaceFrame1->setDropTime(sfStartTime);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -1528,7 +1530,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame);
     mFrameTimeline->setSfPresent(26, presentFence1);
@@ -1552,11 +1554,11 @@
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
-    Fps vsyncRate = Fps::fromPeriodNsecs(11);
+    Fps vsyncRate = RR_11;
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
     presentFence1->signalForTest(30);
@@ -1566,7 +1568,7 @@
 
     // Trigger a flush by finalizing the next DisplayFrame
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(56, presentFence2);
     displayFrame = getDisplayFrame(0);
 
@@ -1591,11 +1593,11 @@
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
-    Fps vsyncRate = Fps::fromPeriodNsecs(11);
+    Fps vsyncRate = RR_11;
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
     presentFence1->signalForTest(50);
@@ -1605,7 +1607,7 @@
 
     // Trigger a flush by finalizing the next DisplayFrame
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(56, presentFence2);
     displayFrame = getDisplayFrame(0);
 
@@ -1633,7 +1635,7 @@
 TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishEarlyPresent) {
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({12, 18, 40});
-    mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
 
     mFrameTimeline->setSfPresent(22, presentFence1);
     auto displayFrame = getDisplayFrame(0);
@@ -1673,7 +1675,7 @@
     int64_t sfToken4 = mTokenManager->generateTokenForPredictions({112, 120, 120});
 
     // case 1 - cpu time = 33 - 12 = 21, vsync period = 11
-    mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
     mFrameTimeline->setSfPresent(33, presentFence1, gpuFence1);
     auto displayFrame0 = getDisplayFrame(0);
     gpuFence1->signalForTest(36);
@@ -1683,7 +1685,7 @@
     EXPECT_EQ(displayFrame0->getActuals().presentTime, 0);
 
     // case 2 - cpu time = 56 - 52 = 4, vsync period = 30
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
     mFrameTimeline->setSfPresent(56, presentFence2, gpuFence2);
     auto displayFrame1 = getDisplayFrame(1);
     gpuFence2->signalForTest(76);
@@ -1697,7 +1699,7 @@
     EXPECT_EQ(displayFrame0->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
 
     // case 3 - cpu time = 86 - 82 = 4, vsync period = 30
-    mFrameTimeline->setSfWakeUp(sfToken3, 106, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken3, 106, RR_30, RR_30);
     mFrameTimeline->setSfPresent(112, presentFence3, gpuFence3);
     auto displayFrame2 = getDisplayFrame(2);
     gpuFence3->signalForTest(116);
@@ -1711,7 +1713,7 @@
     EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
 
     // case 4 - cpu time = 86 - 82 = 4, vsync period = 30
-    mFrameTimeline->setSfWakeUp(sfToken4, 120, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken4, 120, RR_30, RR_30);
     mFrameTimeline->setSfPresent(140, presentFence4, gpuFence4);
     auto displayFrame3 = getDisplayFrame(3);
     gpuFence4->signalForTest(156);
@@ -1748,7 +1750,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(16);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(27, presentFence1);
@@ -1771,7 +1773,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(36);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(57, presentFence2);
@@ -1799,10 +1801,10 @@
     ::testing::Mock::VerifyAndClearExpectations(mTimeStats.get());
 
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{Fps::fromPeriodNsecs(11), std::nullopt, sUidOne,
-                                                   sLayerNameOne, sGameMode,
-                                                   JankType::PredictionError, -3, 5, 0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{RR_11, std::nullopt, sUidOne,
+                                                                sLayerNameOne, sGameMode,
+                                                                JankType::PredictionError, -3, 5,
+                                                                0}));
 
     addEmptyDisplayFrame();
 
@@ -1834,7 +1836,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(16);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(26, presentFence1);
@@ -1857,7 +1859,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(36);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(57, presentFence2);
@@ -1885,10 +1887,10 @@
     ::testing::Mock::VerifyAndClearExpectations(mTimeStats.get());
 
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{Fps::fromPeriodNsecs(11), std::nullopt, sUidOne,
-                                                   sLayerNameOne, sGameMode,
-                                                   JankType::PredictionError, -3, 5, 0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{RR_11, std::nullopt, sUidOne,
+                                                                sLayerNameOne, sGameMode,
+                                                                JankType::PredictionError, -3, 5,
+                                                                0}));
 
     addEmptyDisplayFrame();
 
@@ -1919,7 +1921,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken1, 42, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 42, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(46, presentFence1);
@@ -1966,7 +1968,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(26);
-    mFrameTimeline->setSfWakeUp(sfToken1, 32, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 32, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(36, presentFence1);
@@ -1989,7 +1991,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken2, 43, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 43, RR_11, RR_11);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(56, presentFence2);
@@ -2047,7 +2049,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(50);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, RR_30, RR_30);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(56, presentFence1);
@@ -2070,7 +2072,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(84);
-    mFrameTimeline->setSfWakeUp(sfToken2, 112, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 112, RR_30, RR_30);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(116, presentFence2);
@@ -2131,7 +2133,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(50);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, RR_30, RR_30);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(56, presentFence1);
@@ -2154,7 +2156,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(80);
-    mFrameTimeline->setSfWakeUp(sfToken2, 82, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_30, RR_30);
     // Setting previous latch time to 54, adjusted deadline will be 54 + vsyncTime(30) = 84
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
@@ -2206,7 +2208,7 @@
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60});
 
     // Case 1: cpu time = 33 - 12 = 21, vsync period = 11
-    mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
     mFrameTimeline->setSfPresent(33, presentFence1, gpuFence1);
     auto displayFrame = getDisplayFrame(0);
     gpuFence1->signalForTest(36);
@@ -2225,7 +2227,7 @@
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
 
     // Case 2: No GPU fence so it will not use GPU composition.
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
     mFrameTimeline->setSfPresent(66, presentFence2);
     auto displayFrame2 = getDisplayFrame(2); // 2 because of previous empty frame
     presentFence2->signalForTest(90);
@@ -2250,13 +2252,13 @@
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60});
     int64_t sfToken3 = mTokenManager->generateTokenForPredictions({72, 80, 80});
 
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     mFrameTimeline->setSfPresent(26, erroneousPresentFence1);
 
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
     mFrameTimeline->setSfPresent(60, erroneousPresentFence2);
 
-    mFrameTimeline->setSfWakeUp(sfToken3, 72, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken3, 72, RR_11, RR_11);
     mFrameTimeline->setSfPresent(80, validPresentFence);
 
     erroneousPresentFence2->signalForTest(2);
@@ -2479,4 +2481,44 @@
     mFrameTimeline->setSfPresent(50, presentFence);
     ASSERT_EQ(surfaceFrame->getBaseTime(), 50);
 }
+
+TEST_F(FrameTimelineTest, surfaceFrameRenderRateUsingDisplayRate) {
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    FrameTimelineInfo ftInfo;
+    ftInfo.vsyncId = token1;
+    ftInfo.inputEventId = sInputEventId;
+    auto surfaceFrame =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_30, RR_11);
+    surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame);
+    presentFence1->signalForTest(std::chrono::nanoseconds(50ns).count());
+    mFrameTimeline->setSfPresent(50, presentFence1);
+
+    EXPECT_EQ(surfaceFrame->getRenderRate().getPeriodNsecs(), 11);
+}
+
+TEST_F(FrameTimelineTest, surfaceFrameRenderRateUsingAppFrameRate) {
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    FrameTimelineInfo ftInfo;
+    ftInfo.vsyncId = token1;
+    ftInfo.inputEventId = sInputEventId;
+    auto surfaceFrame =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    surfaceFrame->setRenderRate(RR_30);
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
+    surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame);
+    presentFence1->signalForTest(std::chrono::nanoseconds(50ns).count());
+    mFrameTimeline->setSfPresent(50, presentFence1);
+
+    EXPECT_EQ(surfaceFrame->getRenderRate().getPeriodNsecs(), 30);
+}
 } // namespace android::frametimeline
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index da00377..8a45f17 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -53,6 +53,7 @@
 using Hwc2::Config;
 
 using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
+using hal::IComposerClient;
 using ::testing::_;
 using ::testing::DoAll;
 using ::testing::ElementsAreArray;
@@ -119,6 +120,157 @@
     }
 }
 
+TEST_F(HWComposerTest, getModesWithLegacyDisplayConfigs) {
+    constexpr hal::HWDisplayId kHwcDisplayId = 2;
+    constexpr hal::HWConfigId kConfigId = 42;
+    constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+
+    expectHotplugConnect(kHwcDisplayId);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(false));
+
+    {
+        EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+                .WillOnce(Return(HalError::BAD_DISPLAY));
+        EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+    }
+    {
+        constexpr int32_t kWidth = 480;
+        constexpr int32_t kHeight = 720;
+        constexpr int32_t kConfigGroup = 1;
+        constexpr int32_t kVsyncPeriod = 16666667;
+
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
+                                        _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kWidth), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::HEIGHT, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kHeight), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::CONFIG_GROUP, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kConfigGroup), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::VSYNC_PERIOD, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kVsyncPeriod), Return(HalError::NONE)));
+
+        // Optional Parameters UNSUPPORTED
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+                                        _))
+                .WillOnce(Return(HalError::UNSUPPORTED));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+                                        _))
+                .WillOnce(Return(HalError::UNSUPPORTED));
+
+        EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{kConfigId}),
+                                      Return(HalError::NONE)));
+
+        auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().dpiX, -1);
+        EXPECT_EQ(modes.front().dpiY, -1);
+
+        // Optional parameters are supported
+        constexpr int32_t kDpi = 320;
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+                                        _))
+                .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+                                        _))
+                .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        // DPI values are scaled by 1000 in the legacy implementation.
+        EXPECT_EQ(modes.front().dpiX, kDpi / 1000.f);
+        EXPECT_EQ(modes.front().dpiY, kDpi / 1000.f);
+    }
+}
+
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations) {
+    constexpr hal::HWDisplayId kHwcDisplayId = 2;
+    constexpr hal::HWConfigId kConfigId = 42;
+    constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+    expectHotplugConnect(kHwcDisplayId);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(true));
+
+    {
+        EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+                .WillOnce(Return(HalError::BAD_DISPLAY));
+        EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+    }
+    {
+        constexpr int32_t kWidth = 480;
+        constexpr int32_t kHeight = 720;
+        constexpr int32_t kConfigGroup = 1;
+        constexpr int32_t kVsyncPeriod = 16666667;
+        hal::DisplayConfiguration displayConfiguration;
+        displayConfiguration.configId = kConfigId;
+        displayConfiguration.configGroup = kConfigGroup;
+        displayConfiguration.height = kHeight;
+        displayConfiguration.width = kWidth;
+        displayConfiguration.vsyncPeriod = kVsyncPeriod;
+
+        EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+                .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+                                        displayConfiguration}),
+                                Return(HalError::NONE)));
+
+        // Optional dpi not supported
+        auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().dpiX, -1);
+        EXPECT_EQ(modes.front().dpiY, -1);
+
+        // Supports optional dpi parameter
+        constexpr int32_t kDpi = 320;
+        displayConfiguration.dpi = {kDpi, kDpi};
+
+        EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+                .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+                                        displayConfiguration}),
+                                Return(HalError::NONE)));
+
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().dpiX, kDpi);
+        EXPECT_EQ(modes.front().dpiY, kDpi);
+    }
+}
+
 TEST_F(HWComposerTest, onVsync) {
     constexpr hal::HWDisplayId kHwcDisplayId = 1;
     expectHotplugConnect(kHwcDisplayId);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index e4f49e8..95f1940 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -736,4 +736,22 @@
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
 }
 
+TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2, 3,
+                                      1, 11, 111, 13, 14, 11, 111, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+    expected = {12, 121, 122, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 4301186..48f8923 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -17,10 +17,15 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <gui/fake/BufferData.h>
+#include <renderengine/mock/FakeExternalTexture.h>
+#include <ui/ShadowSettings.h>
+
 #include "Client.h" // temporarily needed for LayerCreationArgs
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHierarchy.h"
 #include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -168,7 +173,7 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
-    void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
+    void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
 
     void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
         if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
@@ -308,7 +313,218 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setFrameRateSelectionPriority(uint32_t id, int32_t priority) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateSelectionPriority;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateSelectionPriority = priority;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRate(uint32_t id, float frameRate, int8_t compatibility,
+                      int8_t changeFrameRateStrategy) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRate = frameRate;
+        transactions.back().states.front().state.frameRateCompatibility = compatibility;
+        transactions.back().states.front().state.changeFrameRateStrategy = changeFrameRateStrategy;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRateCategory(uint32_t id, int8_t frameRateCategory) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateCategoryChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateCategory = frameRateCategory;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eFrameRateSelectionStrategyChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDefaultFrameRateCompatibility(uint32_t id, int8_t defaultFrameRateCompatibility) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eDefaultFrameRateCompatibilityChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.defaultFrameRateCompatibility =
+                defaultFrameRateCompatibility;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setRoundedCorners(uint32_t id, float radius) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eCornerRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.cornerRadius = radius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setBuffer(uint32_t id, std::shared_ptr<renderengine::ExternalTexture> texture) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBufferChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().externalTexture = texture;
+        transactions.back().states.front().state.bufferData =
+                std::make_shared<fake::BufferData>(texture->getId(), texture->getWidth(),
+                                                   texture->getHeight(), texture->getPixelFormat(),
+                                                   texture->getUsage());
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setBuffer(uint32_t id) {
+        static uint64_t sBufferId = 1;
+        setBuffer(id,
+                  std::make_shared<renderengine::mock::
+                                           FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                                sBufferId++,
+                                                                HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
+    }
+
+    void setBufferCrop(uint32_t id, const Rect& bufferCrop) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBufferCropChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.bufferCrop = bufferCrop;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDamageRegion(uint32_t id, const Region& damageRegion) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eSurfaceDamageRegionChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.surfaceDamageRegion = damageRegion;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDataspace(uint32_t id, ui::Dataspace dataspace) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eDataspaceChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.dataspace = dataspace;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setMatrix(uint32_t id, float dsdx, float dtdx, float dtdy, float dsdy) {
+        layer_state_t::matrix22_t matrix{dsdx, dtdx, dtdy, dsdy};
+
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eMatrixChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.matrix = matrix;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setShadowRadius(uint32_t id, float shadowRadius) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eShadowRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.shadowRadius = shadowRadius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setTrustedOverlay(uint32_t id, bool isTrustedOverlay) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.isTrustedOverlay = isTrustedOverlay;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     LayerLifecycleManager mLifecycleManager;
 };
 
+class LayerSnapshotTestBase : public LayerHierarchyTestBase {
+protected:
+    LayerSnapshotTestBase() : LayerHierarchyTestBase() {}
+
+    void createRootLayer(uint32_t id) override {
+        LayerHierarchyTestBase::createRootLayer(id);
+        setColor(id);
+    }
+
+    void createLayer(uint32_t id, uint32_t parentId) override {
+        LayerHierarchyTestBase::createLayer(id, parentId);
+        setColor(parentId);
+    }
+
+    void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) override {
+        LayerHierarchyTestBase::mirrorLayer(id, parent, layerToMirror);
+        setColor(id);
+    }
+
+    void update(LayerSnapshotBuilder& snapshotBuilder) {
+        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
+                                     mLifecycleManager.getDestroyedLayers());
+        }
+        LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                        .layerLifecycleManager = mLifecycleManager,
+                                        .includeMetadata = false,
+                                        .displays = mFrontEndDisplayInfos,
+                                        .displayChanges = mHasDisplayChanges,
+                                        .globalShadowSettings = globalShadowSettings,
+                                        .supportsBlur = true,
+                                        .supportedLayerGenericMetadata = {},
+                                        .genericLayerMetadataKeyMap = {}};
+        snapshotBuilder.update(args);
+
+        mLifecycleManager.commitChanges();
+    }
+
+    LayerHierarchyBuilder mHierarchyBuilder{{}};
+
+    DisplayInfos mFrontEndDisplayInfos;
+    bool mHasDisplayChanges = false;
+
+    ShadowSettings globalShadowSettings;
+};
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
new file mode 100644
index 0000000..631adf1
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -0,0 +1,971 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerHistoryIntegrationTest"
+
+#include <Layer.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include <renderengine/mock/FakeExternalTexture.h>
+
+#include "FpsOps.h"
+#include "LayerHierarchyTest.h"
+#include "Scheduler/LayerHistory.h"
+#include "Scheduler/LayerInfo.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockSchedulerCallback.h"
+
+namespace android::scheduler {
+
+using android::mock::createDisplayMode;
+
+class LayerHistoryIntegrationTest : public surfaceflinger::frontend::LayerSnapshotTestBase {
+protected:
+    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
+    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::kMaxPeriodForFrequentLayerNs;
+    static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::kFrequentLayerWindowSize;
+    static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
+
+    static constexpr Fps LO_FPS = 30_Hz;
+    static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
+
+    static constexpr Fps HI_FPS = 90_Hz;
+    static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
+
+    LayerHistoryIntegrationTest() : LayerSnapshotTestBase() {
+        mFlinger.resetScheduler(mScheduler);
+        mLifecycleManager = {};
+        mHierarchyBuilder = {{}};
+    }
+
+    void updateLayerSnapshotsAndLayerHistory(nsecs_t now) {
+        LayerSnapshotTestBase::update(mFlinger.mutableLayerSnapshotBuilder());
+        mFlinger.updateLayerHistory(now);
+    }
+
+    void setBufferWithPresentTime(sp<Layer>& layer, nsecs_t time) {
+        uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+        setBuffer(sequence);
+        layer->setDesiredPresentTime(time, false /*autotimestamp*/);
+        updateLayerSnapshotsAndLayerHistory(time);
+    }
+
+    LayerHistory& history() { return mScheduler->mutableLayerHistory(); }
+    const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
+
+    LayerHistory::Summary summarizeLayerHistory(nsecs_t now) {
+        // LayerHistory::summarize makes no guarantee of the order of the elements in the summary
+        // however, for testing only, a stable order is required, therefore we sort the list here.
+        // Any tests requiring ordered results must create layers with names.
+        auto summary = history().summarize(*mScheduler->refreshRateSelector(), now);
+        std::sort(summary.begin(), summary.end(),
+                  [](const RefreshRateSelector::LayerRequirement& lhs,
+                     const RefreshRateSelector::LayerRequirement& rhs) -> bool {
+                      return lhs.name < rhs.name;
+                  });
+        return summary;
+    }
+
+    size_t layerCount() const { return mScheduler->layerHistorySize(); }
+    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS {
+        return history().mActiveLayerInfos.size();
+    }
+
+    auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isFrequent(now).isFrequent;
+        });
+    }
+
+    auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isAnimating(now);
+        });
+    }
+
+    auto clearLayerHistoryCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isFrequent(now).clearHistory;
+        });
+    }
+
+    void setDefaultLayerVote(Layer* layer,
+                             LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+        auto [found, layerPair] = history().findLayer(layer->getSequence());
+        if (found != LayerHistory::LayerStatus::NotFound) {
+            layerPair->second->setDefaultLayerVote(vote);
+        }
+    }
+
+    auto createLegacyAndFrontedEndLayer(uint32_t sequence) {
+        std::string layerName = "test layer:" + std::to_string(sequence);
+        const auto layer =
+                sp<Layer>::make(LayerCreationArgs{mFlinger.flinger(),
+                                                  nullptr,
+                                                  layerName,
+                                                  0,
+                                                  {},
+                                                  std::make_optional<uint32_t>(sequence)});
+        mFlinger.injectLegacyLayer(layer);
+        createRootLayer(sequence);
+        return layer;
+    }
+
+    auto destroyLayer(sp<Layer>& layer) {
+        uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+        mFlinger.releaseLegacyLayer(sequence);
+        layer.clear();
+        destroyLayerHandle(sequence);
+    }
+
+    void recordFramesAndExpect(sp<Layer>& layer, nsecs_t& time, Fps frameRate,
+                               Fps desiredRefreshRate, int numFrames) {
+        LayerHistory::Summary summary;
+        for (int i = 0; i < numFrames; i++) {
+            setBufferWithPresentTime(layer, time);
+            time += frameRate.getPeriodNsecs();
+
+            summary = summarizeLayerHistory(time);
+        }
+
+        ASSERT_EQ(1u, summary.size());
+        ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+        ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
+    }
+
+    std::shared_ptr<RefreshRateSelector> mSelector =
+            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
+                                                                              LO_FPS),
+                                                            createDisplayMode(DisplayModeId(1),
+                                                                              HI_FPS)),
+                                                  DisplayModeId(0));
+
+    mock::SchedulerCallback mSchedulerCallback;
+
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+
+    TestableSurfaceFlinger mFlinger;
+};
+
+namespace {
+
+TEST_F(LayerHistoryIntegrationTest, singleLayerNoVoteDefaultCompatibility) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    // No layers returned if no layers are active.
+    EXPECT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1);
+    setDefaultFrameRateCompatibility(1, ANATIVEWINDOW_FRAME_RATE_NO_VOTE);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, singleLayerMinVoteDefaultCompatibility) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    EXPECT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1);
+    setDefaultFrameRateCompatibility(1, ANATIVEWINDOW_FRAME_RATE_MIN);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    auto summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneInvisibleLayer) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    // Layer is still considered inactive so we expect to get Min
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+
+    hideLayer(1);
+    setBuffer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVote) {
+    createLegacyAndFrontedEndLayer(1);
+    setFrameRate(1, 73.4f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitExactVote) {
+    createLegacyAndFrontedEndLayer(1);
+    setFrameRate(1, 73.4f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) {
+    createLegacyAndFrontedEndLayer(1);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    // First LayerRequirement is the frame rate specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, multipleLayers) {
+    auto layer1 = createLegacyAndFrontedEndLayer(1);
+    auto layer2 = createLegacyAndFrontedEndLayer(2);
+    auto layer3 = createLegacyAndFrontedEndLayer(3);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(3u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    LayerHistory::Summary summary;
+
+    // layer1 is active but infrequent.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer1, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer2 is frequent and has high refresh rate.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer2, time);
+        time += HI_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    // layer1 is still active but infrequent.
+    setBufferWithPresentTime(layer1, time);
+
+    ASSERT_EQ(2u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_EQ(HI_FPS, summarizeLayerHistory(time)[1].desiredRefreshRate);
+
+    EXPECT_EQ(2u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer1 is no longer active.
+    // layer2 is frequent and has low refresh rate.
+    for (size_t i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer2, time);
+        time += LO_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 still has low refresh rate.
+    // layer3 has high refresh rate but not enough history.
+    constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+        if (i % RATIO == 0) {
+            setBufferWithPresentTime(layer2, time);
+        }
+
+        setBufferWithPresentTime(layer3, time);
+        time += HI_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(2u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
+    EXPECT_EQ(2u, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer3 becomes recently active.
+    setBufferWithPresentTime(layer3, time);
+    summary = summarizeLayerHistory(time);
+    ASSERT_EQ(2u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+    EXPECT_EQ(2u, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer1 expires.
+    destroyLayer(layer1);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    ASSERT_EQ(2u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+    EXPECT_EQ(2u, layerCount());
+    EXPECT_EQ(2u, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer2 still has low refresh rate.
+    // layer3 becomes inactive.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer2, time);
+        time += LO_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 expires.
+    destroyLayer(layer2);
+    updateLayerSnapshotsAndLayerHistory(time);
+    summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summary.empty());
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer3 becomes active and has high refresh rate.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
+        setBufferWithPresentTime(layer3, time);
+        time += HI_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(HI_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer3 expires.
+    destroyLayer(layer3);
+    updateLayerSnapshotsAndLayerHistory(time);
+    summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summary.empty());
+    EXPECT_EQ(0u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, inactiveLayers) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+
+    // the very first updates makes the layer frequent
+    for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+        EXPECT_EQ(1u, layerCount());
+        ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1u, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
+    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    setBufferWithPresentTime(layer, time);
+
+    EXPECT_EQ(1u, layerCount());
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // advance the time for the previous frame to be inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+
+    // Now even if we post a quick few frame we should stay infrequent
+    for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+
+        EXPECT_EQ(1u, layerCount());
+        ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1u, activeLayerCount());
+        EXPECT_EQ(0, frequentLayerCount(time));
+    }
+
+    // More quick frames will get us to frequent again
+    setBufferWithPresentTime(layer, time);
+    time += HI_FPS_PERIOD;
+
+    EXPECT_EQ(1u, layerCount());
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayer) {
+    auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
+    auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
+    hideLayer(2);
+    setFrameRate(1, 60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRate(2, 90.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    nsecs_t time = systemTime();
+
+    // Post a buffer to the layers to make them active
+    setBufferWithPresentTime(explicitVisiblelayer, time);
+    setBufferWithPresentTime(explicitInvisiblelayer, time);
+
+    EXPECT_EQ(2u, layerCount());
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(2u, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, infrequentAnimatingLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // another update with the same cadence keep in infrequent
+    setBufferWithPresentTime(layer, time);
+    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->changes |=
+            frontend::RequestedLayerState::Changes::Animation;
+    mFlinger.updateLayerHistory(time);
+    // an update as animation will immediately vote for Max
+    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(1, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, frequentLayerBecomingInfrequentAndBack) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // Fill up the window with frequent updates
+    for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += (60_Hz).getPeriodNsecs();
+
+        EXPECT_EQ(1u, layerCount());
+        ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1u, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // posting a buffer after long inactivity should retain the layer as active
+    time += std::chrono::nanoseconds(3s).count();
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more infrequent buffer should make the layer infrequent
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    setBufferWithPresentTime(layer, time);
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting another buffer should keep the layer infrequent
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more buffers would mean starting of an animation, so making the layer frequent
+    setBufferWithPresentTime(layer, time);
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(1, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting a buffer after long inactivity should retain the layer as active
+    time += std::chrono::nanoseconds(3s).count();
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting another buffer should keep the layer frequent
+    time += (60_Hz).getPeriodNsecs();
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, inconclusiveLayerBecomingFrequent) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // Fill up the window with frequent updates
+    for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += (60_Hz).getPeriodNsecs();
+
+        EXPECT_EQ(1u, layerCount());
+        ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1u, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // posting infrequent buffers after long inactivity should make the layer
+    // inconclusive but frequent.
+    time += std::chrono::nanoseconds(3s).count();
+    setBufferWithPresentTime(layer, time);
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more buffers should make the layer frequent and switch the refresh rate to max
+    // by clearing the history
+    setBufferWithPresentTime(layer, time);
+    setBufferWithPresentTime(layer, time);
+    setBufferWithPresentTime(layer, time);
+    EXPECT_EQ(1, clearLayerHistoryCount(time));
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, getFramerate) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    float expectedFramerate = 1e9f / MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    EXPECT_FLOAT_EQ(expectedFramerate, history().getLayerFramerate(time, layer->getSequence()));
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayer60Hz) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+    for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+        recordFramesAndExpect(layer, time, Fps::fromValue(fps), 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+    }
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayer60_30Hz) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+
+    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayerNotOscillating) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryIntegrationTest, smallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    LayerHistory::Summary summary;
+
+    // layer is active but infrequent.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        auto props = layer->getLayerProps();
+        if (i % 3 == 0) {
+            props.isSmallDirty = false;
+        } else {
+            props.isSmallDirty = true;
+        }
+
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_GE(HI_FPS, summary[0].desiredRefreshRate);
+}
+
+TEST_F(LayerHistoryIntegrationTest, DISABLED_smallDirtyInMultiLayer) {
+    auto uiLayer = createLegacyAndFrontedEndLayer(1);
+    auto videoLayer = createLegacyAndFrontedEndLayer(2);
+    setFrameRate(2, 30.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(2u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    LayerHistory::Summary summary;
+
+    // uiLayer is updating small dirty.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
+        auto props = uiLayer->getLayerProps();
+        props.isSmallDirty = true;
+        setBuffer(1);
+        uiLayer->setDesiredPresentTime(0, false /*autotimestamp*/);
+        updateLayerSnapshotsAndLayerHistory(time);
+        setBufferWithPresentTime(videoLayer, time);
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
+}
+
+class LayerHistoryIntegrationTestParameterized
+      : public LayerHistoryIntegrationTest,
+        public testing::WithParamInterface<std::chrono::nanoseconds> {};
+
+TEST_P(LayerHistoryIntegrationTestParameterized, HeuristicLayerWithInfrequentLayer) {
+    std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
+    auto heuristicLayer = createLegacyAndFrontedEndLayer(1);
+    auto infrequentLayer = createLegacyAndFrontedEndLayer(2);
+
+    const nsecs_t startTime = systemTime();
+
+    const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
+    setBufferWithPresentTime(heuristicLayer, startTime);
+    setBufferWithPresentTime(infrequentLayer, startTime);
+
+    nsecs_t time = startTime;
+    nsecs_t lastInfrequentUpdate = startTime;
+    const size_t totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
+    size_t infrequentLayerUpdates = 0;
+    while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
+        time += heuristicUpdateDelta.count();
+        setBufferWithPresentTime(heuristicLayer, time);
+
+        if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
+            ALOGI("submitting infrequent frame [%zu/%zu]", infrequentLayerUpdates,
+                  totalInfrequentLayerUpdates);
+            lastInfrequentUpdate = time;
+            setBufferWithPresentTime(infrequentLayer, time);
+            infrequentLayerUpdates++;
+        }
+
+        if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
+            ASSERT_NE(0u, summarizeLayerHistory(time).size());
+            ASSERT_GE(2u, summarizeLayerHistory(time).size());
+
+            bool max = false;
+            bool min = false;
+            Fps heuristic;
+            for (const auto& layer : summarizeLayerHistory(time)) {
+                if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
+                    heuristic = layer.desiredRefreshRate;
+                } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
+                    max = true;
+                } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
+                    min = true;
+                }
+            }
+
+            if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
+                EXPECT_EQ(24_Hz, heuristic);
+                EXPECT_FALSE(max);
+                if (summarizeLayerHistory(time).size() == 2) {
+                    EXPECT_TRUE(min);
+                }
+            }
+        }
+    }
+}
+
+class SmallAreaDetectionTest : public LayerHistoryIntegrationTest {
+protected:
+    static constexpr int32_t DISPLAY_WIDTH = 100;
+    static constexpr int32_t DISPLAY_HEIGHT = 100;
+
+    static constexpr int32_t kAppId1 = 10100;
+    static constexpr int32_t kAppId2 = 10101;
+
+    static constexpr float kThreshold1 = 0.05f;
+    static constexpr float kThreshold2 = 0.07f;
+
+    SmallAreaDetectionTest() : LayerHistoryIntegrationTest() {
+        std::vector<std::pair<int32_t, float>> mappings;
+        mappings.reserve(2);
+        mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+        mappings.push_back(std::make_pair(kAppId2, kThreshold2));
+
+        mFlinger.enableNewFrontEnd();
+
+        mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
+        mScheduler->updateSmallAreaDetection(mappings);
+    }
+
+    auto createLegacyAndFrontedEndLayer(uint32_t sequence) {
+        std::string layerName = "test layer:" + std::to_string(sequence);
+
+        LayerCreationArgs args = LayerCreationArgs{mFlinger.flinger(),
+                                                   nullptr,
+                                                   layerName,
+                                                   0,
+                                                   {},
+                                                   std::make_optional<uint32_t>(sequence)};
+        args.ownerUid = kAppId1;
+        args.metadata.setInt32(gui::METADATA_WINDOW_TYPE, 2); // APPLICATION
+        const auto layer = sp<Layer>::make(args);
+        mFlinger.injectLegacyLayer(layer);
+        createRootLayer(sequence);
+        return layer;
+    }
+};
+
+TEST_F(SmallAreaDetectionTest, SmallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(10, 10)));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, NotSmallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(50, 50)));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, smallDirtyLayerWithMatrix) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // Original damage region is a small dirty.
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(20, 20)));
+    updateLayerSnapshotsAndLayerHistory(time);
+    ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+
+    setMatrix(sequence, 2.0f, 0, 0, 2.0f);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    // Verify if the small dirty is scaled.
+    ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryIntegrationTestParameterized,
+                        ::testing::Values(1s, 2s, 3s, 4s, 5s));
+
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 69128c0..33c1d86 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -45,6 +45,8 @@
 
 using android::mock::createDisplayMode;
 
+// WARNING: LEGACY TESTS FOR LEGACY FRONT END
+// Update LayerHistoryIntegrationTest instead
 class LayerHistoryTest : public testing::Test {
 protected:
     static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
@@ -152,7 +154,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
     EXPECT_CALL(*layer, getDefaultFrameRateCompatibility())
-            .WillOnce(Return(LayerInfo::FrameRateCompatibility::NoVote));
+            .WillOnce(Return(FrameRateCompatibility::NoVote));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -165,7 +167,10 @@
 
     history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
                      LayerHistory::LayerUpdateType::Buffer);
-    history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */);
+    history().setDefaultFrameRateCompatibility(layer->getSequence(),
+
+                                               layer->getDefaultFrameRateCompatibility(),
+                                               true /* contentDetectionEnabled */);
 
     EXPECT_TRUE(summarizeLayerHistory(time).empty());
     EXPECT_EQ(1, activeLayerCount());
@@ -176,7 +181,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
     EXPECT_CALL(*layer, getDefaultFrameRateCompatibility())
-            .WillOnce(Return(LayerInfo::FrameRateCompatibility::Min));
+            .WillOnce(Return(FrameRateCompatibility::Min));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -188,7 +193,9 @@
 
     history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
                      LayerHistory::LayerUpdateType::Buffer);
-    history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */);
+    history().setDefaultFrameRateCompatibility(layer->getSequence(),
+                                               layer->getDefaultFrameRateCompatibility(),
+                                               true /* contentDetectionEnabled */);
 
     auto summary = summarizeLayerHistory(time);
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -437,6 +444,117 @@
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(0_Hz, Layer::FrameRateCompatibility::Default,
+                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    // First LayerRequirement is the frame rate specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
+// the category is NoPreference.
+TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate(0_Hz, Layer::FrameRateCompatibility::Default,
+                                                    Seamlessness::OnlySeamless,
+                                                    FrameRateCategory::NoPreference)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    EXPECT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default,
+                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There are 2 LayerRequirement's due to the frame rate category.
+    ASSERT_EQ(2, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    // First LayerRequirement is the layer's category specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // Second LayerRequirement is the frame rate specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[1].vote);
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[1].frameRateCategory);
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(2, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
 TEST_F(LayerHistoryTest, multipleLayers) {
     auto layer1 = createLayer("A");
     auto layer2 = createLayer("B");
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 5c2d2e1..11072bc 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -24,13 +24,23 @@
 #include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/MockSchedulerCallback.h"
 
 namespace android::scheduler {
 
+using android::mock::createDisplayMode;
+
 class LayerInfoTest : public testing::Test {
 protected:
     using FrameTimeData = LayerInfo::FrameTimeData;
 
+    static constexpr Fps LO_FPS = 30_Hz;
+    static constexpr Fps HI_FPS = 90_Hz;
+
+    LayerInfoTest() { mFlinger.resetScheduler(mScheduler); }
+
     void setFrameTimes(const std::deque<FrameTimeData>& frameTimes) {
         layerInfo.mFrameTimes = frameTimes;
     }
@@ -43,6 +53,16 @@
     auto calculateAverageFrameTime() { return layerInfo.calculateAverageFrameTime(); }
 
     LayerInfo layerInfo{"TestLayerInfo", 0, LayerHistory::LayerVoteType::Heuristic};
+
+    std::shared_ptr<RefreshRateSelector> mSelector =
+            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
+                                                                              LO_FPS),
+                                                            createDisplayMode(DisplayModeId(1),
+                                                                              HI_FPS)),
+                                                  DisplayModeId(0));
+    mock::SchedulerCallback mSchedulerCallback;
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+    TestableSurfaceFlinger mFlinger;
 };
 
 namespace {
@@ -171,5 +191,75 @@
     ASSERT_EQ(kExpectedFps, Fps::fromPeriodNsecs(*averageFrameTime));
 }
 
+TEST_F(LayerInfoTest, getRefreshRateVote_explicitVote) {
+    LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+                                 .fps = 20_Hz};
+    layerInfo.setLayerVote(vote);
+
+    auto actualVotes =
+            layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+    ASSERT_EQ(actualVotes.size(), 1u);
+    ASSERT_EQ(actualVotes[0].type, vote.type);
+    ASSERT_EQ(actualVotes[0].fps, vote.fps);
+    ASSERT_EQ(actualVotes[0].seamlessness, vote.seamlessness);
+    ASSERT_EQ(actualVotes[0].category, vote.category);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_explicitVoteWithCategory) {
+    LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+                                 .fps = 20_Hz,
+                                 .category = FrameRateCategory::High};
+    layerInfo.setLayerVote(vote);
+
+    auto actualVotes =
+            layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+    ASSERT_EQ(actualVotes.size(), 2u);
+    ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+    ASSERT_EQ(actualVotes[0].category, vote.category);
+    ASSERT_EQ(actualVotes[1].type, vote.type);
+    ASSERT_EQ(actualVotes[1].fps, vote.fps);
+    ASSERT_EQ(actualVotes[1].seamlessness, vote.seamlessness);
+    ASSERT_EQ(actualVotes[1].category, vote.category);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_explicitCategory) {
+    LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+                                 .category = FrameRateCategory::High};
+    layerInfo.setLayerVote(vote);
+
+    auto actualVotes =
+            layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+    ASSERT_EQ(actualVotes.size(), 1u);
+    ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+    ASSERT_EQ(actualVotes[0].category, vote.category);
+    ASSERT_EQ(actualVotes[0].fps, 0_Hz);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_categoryNoPreference) {
+    LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+                                 .category = FrameRateCategory::NoPreference};
+    layerInfo.setLayerVote(vote);
+
+    auto actualVotes =
+            layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+    ASSERT_EQ(actualVotes.size(), 1u);
+    ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+    ASSERT_EQ(actualVotes[0].category, vote.category);
+    ASSERT_EQ(actualVotes[0].fps, 0_Hz);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_noData) {
+    LayerInfo::LayerVote vote = {
+            .type = LayerHistory::LayerVoteType::Heuristic,
+    };
+    layerInfo.setLayerVote(vote);
+
+    auto actualVotes =
+            layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+    ASSERT_EQ(actualVotes.size(), 1u);
+    ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::Max);
+    ASSERT_EQ(actualVotes[0].fps, vote.fps);
+}
+
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 97ef5a2..aecfcba 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -17,6 +17,8 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <renderengine/mock/FakeExternalTexture.h>
+
 #include "FrontEnd/LayerLifecycleManager.h"
 #include "LayerHierarchyTest.h"
 #include "TransactionState.h"
@@ -25,6 +27,8 @@
 
 namespace android::surfaceflinger::frontend {
 
+using namespace ftl::flag_operators;
+
 // To run test:
 /**
  mp :libsurfaceflinger_unittest && adb sync; adb shell \
@@ -84,7 +88,7 @@
     layers.emplace_back(rootLayer(2));
     layers.emplace_back(rootLayer(3));
     lifecycleManager.addLayers(std::move(layers));
-    lifecycleManager.onHandlesDestroyed({1, 2, 3});
+    lifecycleManager.onHandlesDestroyed({{1, "1"}, {2, "2"}, {3, "3"}});
     EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     lifecycleManager.commitChanges();
     EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
@@ -133,7 +137,7 @@
     layers.emplace_back(rootLayer(1));
     layers.emplace_back(rootLayer(2));
     lifecycleManager.addLayers(std::move(layers));
-    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.onHandlesDestroyed({{1, "1"}});
     lifecycleManager.commitChanges();
 
     SCOPED_TRACE("layerWithoutHandleIsDestroyed");
@@ -149,7 +153,7 @@
     layers.emplace_back(rootLayer(1));
     layers.emplace_back(rootLayer(2));
     lifecycleManager.addLayers(std::move(layers));
-    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.onHandlesDestroyed({{1, "1"}});
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({1, 2});
     listener->expectLayersDestroyed({1});
@@ -173,7 +177,7 @@
     listener->expectLayersAdded({});
     listener->expectLayersDestroyed({});
 
-    lifecycleManager.onHandlesDestroyed({3});
+    lifecycleManager.onHandlesDestroyed({{3, "3"}});
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({});
     listener->expectLayersDestroyed({3});
@@ -194,7 +198,7 @@
     listener->expectLayersDestroyed({});
 
     lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
-    lifecycleManager.onHandlesDestroyed({3});
+    lifecycleManager.onHandlesDestroyed({{3, "3"}});
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({});
     listener->expectLayersDestroyed({3});
@@ -215,7 +219,7 @@
     listener->expectLayersDestroyed({});
 
     lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
-    lifecycleManager.onHandlesDestroyed({3, 4});
+    lifecycleManager.onHandlesDestroyed({{3, "3"}, {4, "4"}});
     lifecycleManager.commitChanges();
     listener->expectLayersAdded({});
     listener->expectLayersDestroyed({3, 4});
@@ -376,7 +380,7 @@
     transactions.back().states.front().layerId = 1;
     transactions.emplace_back();
     lifecycleManager.applyTransactions(transactions);
-    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.onHandlesDestroyed({{1, "1"}});
 
     ASSERT_EQ(lifecycleManager.getLayers().size(), 0u);
     ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 2u);
@@ -389,4 +393,171 @@
     listener->expectLayersDestroyed({1, bgLayerId});
 }
 
+TEST_F(LayerLifecycleManagerTest, blurSetsVisibilityChangeFlag) {
+    // clear default color on layer so we start with a layer that does not draw anything.
+    setColor(1, {-1.f, -1.f, -1.f});
+    mLifecycleManager.commitChanges();
+
+    // layer has something to draw
+    setBackgroundBlurRadius(1, 2);
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+
+    // layer still has something to draw, so visibility shouldn't change
+    setBackgroundBlurRadius(1, 3);
+    EXPECT_FALSE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+
+    // layer has nothing to draw
+    setBackgroundBlurRadius(1, 0);
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, colorSetsVisibilityChangeFlag) {
+    // clear default color on layer so we start with a layer that does not draw anything.
+    setColor(1, {-1.f, -1.f, -1.f});
+    mLifecycleManager.commitChanges();
+
+    // layer has something to draw
+    setColor(1, {2.f, 3.f, 4.f});
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+
+    // layer still has something to draw, so visibility shouldn't change
+    setColor(1, {0.f, 0.f, 0.f});
+    EXPECT_FALSE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+
+    // layer has nothing to draw
+    setColor(1, {-1.f, -1.f, -1.f});
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, layerOpacityChangesSetsVisibilityChangeFlag) {
+    // add a default buffer and make the layer opaque
+    setFlags(1, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               1ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+
+    mLifecycleManager.commitChanges();
+
+    // set new buffer but layer opacity doesn't change
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               2ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+              ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
+                                                       RequestedLayerState::Changes::Content)
+                      .get());
+    mLifecycleManager.commitChanges();
+
+    // change layer flags and confirm visibility flag is set
+    setFlags(1, layer_state_t::eLayerOpaque, 0);
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, bufferFormatChangesSetsVisibilityChangeFlag) {
+    // add a default buffer and make the layer opaque
+    setFlags(1, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               1ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+
+    mLifecycleManager.commitChanges();
+
+    // set new buffer with an opaque buffer format
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               2ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGB_888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+              ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
+                                                       RequestedLayerState::Changes::Content |
+                                                       RequestedLayerState::Changes::VisibleRegion |
+                                                       RequestedLayerState::Changes::Visibility)
+                      .get());
+    mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, roundedCornerChangesSetsVisibilityChangeFlag) {
+    // add a default buffer and make the layer opaque
+    setFlags(1, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               1ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+
+    mLifecycleManager.commitChanges();
+
+    // add rounded corners which should make the layer translucent
+    setRoundedCorners(1, 5.f);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::AffectsChildren |
+                      RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::Geometry |
+                      RequestedLayerState::Changes::VisibleRegion)
+                      .get());
+    mLifecycleManager.commitChanges();
+}
+
+// Even when it does not change visible region, we should mark alpha changes as affecting
+// visible region because HWC impl depends on it. writeOutputIndependentGeometryStateToHWC
+// is only called if we are updating geometry.
+TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) {
+    mLifecycleManager.commitChanges();
+    float startingAlpha = 0.5f;
+    setAlpha(1, startingAlpha);
+
+    // this is expected because layer alpha changes from 1 to 0.5, it may no longer be opaque
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::AffectsChildren |
+                      RequestedLayerState::Changes::VisibleRegion)
+                      .string());
+    EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(startingAlpha));
+    mLifecycleManager.commitChanges();
+
+    float endingAlpha = 0.2f;
+    setAlpha(1, endingAlpha);
+
+    // this is not expected but we should make sure this behavior does not change
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::AffectsChildren |
+                      RequestedLayerState::Changes::VisibleRegion)
+                      .string());
+    EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(endingAlpha));
+    mLifecycleManager.commitChanges();
+
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+              ftl::Flags<RequestedLayerState::Changes>().string());
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 5da893e..e784eb7 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -17,10 +17,14 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <renderengine/mock/FakeExternalTexture.h>
+
 #include "FrontEnd/LayerHierarchy.h"
 #include "FrontEnd/LayerLifecycleManager.h"
 #include "FrontEnd/LayerSnapshotBuilder.h"
+#include "Layer.h"
 #include "LayerHierarchyTest.h"
+#include "ui/GraphicTypes.h"
 
 #define UPDATE_AND_VERIFY(BUILDER, ...)                                    \
     ({                                                                     \
@@ -46,33 +50,23 @@
     --gtest_filter="LayerSnapshotTest.*" --gtest_brief=1
 */
 
-class LayerSnapshotTest : public LayerHierarchyTestBase {
+class LayerSnapshotTest : public LayerSnapshotTestBase {
 protected:
-    LayerSnapshotTest() : LayerHierarchyTestBase() {
+    LayerSnapshotTest() : LayerSnapshotTestBase() {
         UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     }
 
-    void createRootLayer(uint32_t id) override {
-        LayerHierarchyTestBase::createRootLayer(id);
-        setColor(id);
-    }
-
-    void createLayer(uint32_t id, uint32_t parentId) override {
-        LayerHierarchyTestBase::createLayer(id, parentId);
-        setColor(parentId);
-    }
-
-    void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) override {
-        LayerHierarchyTestBase::mirrorLayer(id, parent, layerToMirror);
-        setColor(id);
-    }
-
-    void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges,
-                         const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) {
+    void update(LayerSnapshotBuilder& actualBuilder, LayerSnapshotBuilder::Args& args) {
         if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
             mHierarchyBuilder.update(mLifecycleManager.getLayers(),
                                      mLifecycleManager.getDestroyedLayers());
         }
+        args.root = mHierarchyBuilder.getHierarchy();
+        actualBuilder.update(args);
+    }
+
+    void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges,
+                         const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) {
         LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
                                         .layerLifecycleManager = mLifecycleManager,
                                         .includeMetadata = false,
@@ -82,7 +76,7 @@
                                         .supportsBlur = true,
                                         .supportedLayerGenericMetadata = {},
                                         .genericLayerMetadataKeyMap = {}};
-        actualBuilder.update(args);
+        update(actualBuilder, args);
 
         // rebuild layer snapshots from scratch and verify that it matches the updated state.
         LayerSnapshotBuilder expectedBuilder(args);
@@ -102,11 +96,7 @@
     LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath path) {
         return mSnapshotBuilder.getSnapshot(path);
     }
-
-    LayerHierarchyBuilder mHierarchyBuilder{{}};
     LayerSnapshotBuilder mSnapshotBuilder;
-    DisplayInfos mFrontEndDisplayInfos;
-    renderengine::ShadowSettings globalShadowSettings;
     static const std::vector<uint32_t> STARTING_ZORDER;
 };
 const std::vector<uint32_t> LayerSnapshotTest::STARTING_ZORDER = {1,   11,   111, 12, 121,
@@ -292,26 +282,51 @@
     // │   └── 13
     // └── 2
 
-    std::vector<TransactionState> transactions;
-    transactions.emplace_back();
-    transactions.back().states.push_back({});
-    transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
-    transactions.back().states.front().state.frameRate = 90.0;
-    transactions.back().states.front().state.frameRateCompatibility =
-            ANATIVEWINDOW_FRAME_RATE_EXACT;
-    transactions.back().states.front().state.changeFrameRateStrategy =
-            ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS;
-    transactions.back().states.front().layerId = 11;
-    mLifecycleManager.applyTransactions(transactions);
+    setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
-    EXPECT_EQ(getSnapshot(11)->frameRate.rate.getIntValue(), 90);
-    EXPECT_EQ(getSnapshot(11)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::Exact);
-    EXPECT_EQ(getSnapshot(111)->frameRate.rate.getIntValue(), 90);
-    EXPECT_EQ(getSnapshot(111)->frameRate.type,
-              scheduler::LayerInfo::FrameRateCompatibility::Exact);
-    EXPECT_EQ(getSnapshot(1)->frameRate.rate.getIntValue(), 0);
-    EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
+}
+
+TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotesDoesNotAffectSiblings) {
+    // ROOT
+    // ├── 1 (verify layer has no vote)
+    // │   ├── 11 (frame rate set)
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set)
+    // │   │   ├── 121
+    // │   │   └── 122
+    // │   │       └── 1221
+    // │   └── 13 (verify layer has default vote)
+    // └── 2
+
+    setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+    setFrameRate(12, 45.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(12)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(12)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(121)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(121)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(1221)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(1221)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot(13)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(13)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot(2)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(2)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
 }
 
 TEST_F(LayerSnapshotTest, CanCropTouchableRegion) {
@@ -340,16 +355,22 @@
 }
 
 TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
-    static constexpr int blurRadius = 42;
-    setBackgroundBlurRadius(1221, blurRadius);
+    int blurRadius = 42;
+    setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
 
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
 
+    blurRadius = 21;
+    setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+
     static constexpr float alpha = 0.5;
     setAlpha(12, alpha);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius * alpha);
+    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius,
+              static_cast<int>(static_cast<float>(blurRadius) * alpha));
 }
 
 // Display Mirroring Tests
@@ -393,8 +414,8 @@
     std::vector<uint32_t> expected = {1,  11, 111, 13, 2,  3,   1,  11, 111,
                                       13, 2,  4,   1,  11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
-    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 3})->outputFilter.layerStack.id, 3u);
-    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 4})->outputFilter.layerStack.id, 4u);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 3u})->outputFilter.layerStack.id, 3u);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 4u})->outputFilter.layerStack.id, 4u);
 }
 
 // ROOT (DISPLAY 0)
@@ -418,7 +439,7 @@
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch));
     Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}};
-    EXPECT_TRUE(getSnapshot({.id = 111, .mirrorRootId = 3})
+    EXPECT_TRUE(getSnapshot({.id = 111, .mirrorRootIds = 3u})
                         ->inputInfo.touchableRegion.hasSameRects(touchCroppedByMirrorRoot));
 }
 
@@ -445,6 +466,21 @@
     EXPECT_EQ(startingNumSnapshots, mSnapshotBuilder.getSnapshots().size());
 }
 
+TEST_F(LayerSnapshotTest, canMirrorDisplayWithMirrors) {
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 3);
+    expected = {1, 11, 111, 13, 14, 11, 111, 2, 3, 1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->outputFilter.layerStack.id, 0u);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 3u})->outputFilter.layerStack.id, 3u);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 3u, 14u})->outputFilter.layerStack.id, 3u);
+}
+
 // Rel z doesn't create duplicate snapshots but this is for completeness
 TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterRelZ) {
     size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
@@ -467,4 +503,399 @@
     EXPECT_LE(startingNumSnapshots - 2, mSnapshotBuilder.getSnapshots().size());
 }
 
+TEST_F(LayerSnapshotTest, snashotContainsMetadataFromLayerCreationArgs) {
+    LayerCreationArgs args(std::make_optional<uint32_t>(200));
+    args.name = "testlayer";
+    args.addToRoot = true;
+    args.metadata.setInt32(42, 24);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(std::make_unique<RequestedLayerState>(args));
+    EXPECT_TRUE(layers.back()->metadata.has(42));
+    EXPECT_EQ(layers.back()->metadata.getInt32(42, 0), 24);
+    mLifecycleManager.addLayers(std::move(layers));
+
+    std::vector<uint32_t> expected = STARTING_ZORDER;
+    expected.push_back(200);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    EXPECT_TRUE(mSnapshotBuilder.getSnapshot(200)->layerMetadata.has(42));
+    EXPECT_EQ(mSnapshotBuilder.getSnapshot(200)->layerMetadata.getInt32(42, 0), 24);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionPriorityPassedToChildLayers) {
+    setFrameRateSelectionPriority(11, 1);
+
+    setFrameRateSelectionPriority(12, 2);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionPriority, Layer::PRIORITY_UNSET);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionPriority, 1);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionPriority, 2);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionPriority, 2);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionPriority, 2);
+
+    // reparent and verify the child gets the new parent's framerate selection priority
+    reparentLayer(122, 11);
+
+    std::vector<uint32_t> expected = {1, 11, 111, 122, 1221, 12, 121, 13, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionPriority, Layer::PRIORITY_UNSET);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionPriority, 1);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionPriority, 2);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionPriority, 1);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionPriority, 1);
+}
+
+TEST_F(LayerSnapshotTest, framerate) {
+    setFrameRate(11, 244.f, 0, 0);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent is gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer and children get the requested votes
+    EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // reparent and verify the child gets the new parent's framerate
+    reparentLayer(122, 11);
+
+    std::vector<uint32_t> expected = {1, 11, 111, 122, 1221, 12, 121, 13, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+    // verify parent is gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+
+    // verify layer and children get the requested votes
+    EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+
+    EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+
+    EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // reparent and verify the new parent gets no vote
+    reparentLayer(11, 2);
+    expected = {1, 12, 121, 13, 2, 11, 111, 122, 1221};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    // verify old parent has invalid framerate (default)
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify new parent get no vote
+    EXPECT_FALSE(getSnapshot({.id = 2})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 2})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 2})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer and children get the requested votes (unchanged)
+    EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+
+    EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+
+    EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+}
+
+TEST_F(LayerSnapshotTest, translateDataspace) {
+    setDataspace(1, ui::Dataspace::UNKNOWN);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->dataspace, ui::Dataspace::V0_SRGB);
+}
+
+// This test is similar to "frameRate" test case but checks that the setFrameRateCategory API
+// interaction also works correctly with the setFrameRate API within SF frontend.
+TEST_F(LayerSnapshotTest, frameRateWithCategory) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11 (frame rate set to 244.f)
+    // │   │   └── 111
+    // │   ├── 12
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate category set to Normal)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRate(11, 244.f, 0, 0);
+    setFrameRateCategory(122, 3 /* Normal */);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 11 and children 111 get the requested votes
+    EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify parent 12 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 122 and children 1221 get the requested votes
+    EXPECT_FALSE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+    EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Normal);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+    EXPECT_TRUE(
+            getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::AffectsChildren));
+
+    EXPECT_FALSE(getSnapshot({.id = 1221})->frameRate.vote.rate.isValid());
+    EXPECT_TRUE(getSnapshot({.id = 1221})->frameRate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Normal);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+    EXPECT_TRUE(
+            getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::AffectsChildren));
+
+    // reparent and verify the child does NOT get the new parent's framerate because it already has
+    // the frame rate category specified.
+    // ROOT
+    //  ├─1
+    //  │  ├─11 (frame rate set to 244.f)
+    //  │  │  ├─111
+    //  │  │  └─122 (frame rate category set to Normal)
+    //  │  │     └─1221
+    //  │  ├─12
+    //  │  │  └─121
+    //  │  └─13
+    //  └─2
+    reparentLayer(122, 11);
+
+    std::vector<uint32_t> expected = {1, 11, 111, 122, 1221, 12, 121, 13, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+    // verify parent is gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+
+    // verify layer 11 and children 111 get the requested votes
+    EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+
+    EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+
+    // verify layer 122 and children 1221 get the requested category vote (unchanged from
+    // reparenting)
+    EXPECT_FALSE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+    EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Normal);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_FALSE(getSnapshot({.id = 1221})->frameRate.vote.rate.isValid());
+    EXPECT_TRUE(getSnapshot({.id = 1221})->frameRate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Normal);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategy) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set to 244.f with strategy OverrideChildren)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRate(12, 244.f, 0, 0);
+    setFrameRate(122, 123.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
+    setRoundedCorners(1, 42.f);
+    setRoundedCorners(2, 42.f);
+    setCrop(1, Rect{1000, 1000});
+    setCrop(2, Rect{1000, 1000});
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_TRUE(getSnapshot({.id = 1})->roundedCorner.hasRoundedCorners());
+    EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, 42.f);
+    EXPECT_TRUE(getSnapshot({.id = 2})->roundedCorner.hasRoundedCorners());
+
+    // add a buffer with the protected bit, check rounded corners are not set when
+    // skipRoundCornersWhenProtected == true
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               1ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = false,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .displayChanges = false,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {},
+                                    .skipRoundCornersWhenProtected = true};
+    update(mSnapshotBuilder, args);
+    EXPECT_FALSE(getSnapshot({.id = 1})->roundedCorner.hasRoundedCorners());
+    // layer 2 doesn't have a buffer and should be unaffected
+    EXPECT_TRUE(getSnapshot({.id = 2})->roundedCorner.hasRoundedCorners());
+
+    // remove protected bit, check rounded corners are set
+    setBuffer(1,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                                        2ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    update(mSnapshotBuilder, args);
+    EXPECT_TRUE(getSnapshot({.id = 1})->roundedCorner.hasRoundedCorners());
+    EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, 42.f);
+}
+
+TEST_F(LayerSnapshotTest, setRefreshRateIndicatorCompositionType) {
+    setFlags(1, layer_state_t::eLayerIsRefreshRateIndicator,
+             layer_state_t::eLayerIsRefreshRateIndicator);
+    setBuffer(1,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->compositionType,
+              aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR);
+}
+
+TEST_F(LayerSnapshotTest, setBufferCrop) {
+    // validate no buffer but has crop
+    Rect crop = Rect(0, 0, 50, 50);
+    setBufferCrop(1, crop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop);
+
+    setBuffer(1,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(100U /*width*/,
+                                                                        100U /*height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    // validate a buffer crop within the buffer bounds
+    setBufferCrop(1, crop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop);
+
+    // validate a buffer crop outside the buffer bounds
+    crop = Rect(0, 0, 150, 150);
+    setBufferCrop(1, crop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
+
+    // validate no buffer crop
+    setBufferCrop(1, Rect());
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
+}
+
+TEST_F(LayerSnapshotTest, setShadowRadius) {
+    static constexpr float SHADOW_RADIUS = 123.f;
+    setShadowRadius(1, SHADOW_RADIUS);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->shadowSettings.length, SHADOW_RADIUS);
+}
+
+TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) {
+    hideLayer(1);
+    setTrustedOverlay(1, true);
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 0d66d59..85f66f4 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -18,10 +18,10 @@
 #define LOG_TAG "PowerAdvisorTest"
 
 #include <DisplayHardware/PowerAdvisor.h>
-#include <compositionengine/Display.h>
-#include <ftl/fake_guard.h>
+#include <binder/Status.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <powermanager/PowerHalWrapper.h>
 #include <ui/DisplayId.h>
 #include <chrono>
 #include "TestableSurfaceFlinger.h"
@@ -50,7 +50,7 @@
     TestableSurfaceFlinger mFlinger;
     std::unique_ptr<PowerAdvisor> mPowerAdvisor;
     MockPowerHalController* mMockPowerHalController;
-    sp<MockIPowerHintSession> mMockPowerHintSession;
+    std::shared_ptr<MockIPowerHintSession> mMockPowerHintSession;
 };
 
 void PowerAdvisorTest::SetUp() {
@@ -64,13 +64,14 @@
 
 void PowerAdvisorTest::startPowerHintSession() {
     const std::vector<int32_t> threadIds = {1, 2, 3};
-    mMockPowerHintSession = android::sp<NiceMock<MockIPowerHintSession>>::make();
+    mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
     ON_CALL(*mMockPowerHalController, createHintSession)
-            .WillByDefault(
-                    Return(HalResult<sp<IPowerHintSession>>::fromStatus(binder::Status::ok(),
-                                                                        mMockPowerHintSession)));
+            .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
+                                          fromStatus(binder::Status::ok(), mMockPowerHintSession)));
     mPowerAdvisor->enablePowerHintSession(true);
     mPowerAdvisor->startPowerHintSession(threadIds);
+    ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration)
+            .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
 }
 
 void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration,
@@ -123,8 +124,8 @@
     EXPECT_CALL(*mMockPowerHintSession,
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
-            .Times(1);
-
+            .Times(1)
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
     mPowerAdvisor->setDisplays(displayIds);
@@ -163,7 +164,8 @@
     EXPECT_CALL(*mMockPowerHintSession,
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
-            .Times(1);
+            .Times(1)
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -205,7 +207,8 @@
     EXPECT_CALL(*mMockPowerHintSession,
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
-            .Times(1);
+            .Times(1)
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 0397b99..faa12a1 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -26,6 +26,7 @@
 #include <log/log.h>
 #include <ui/Size.h>
 
+#include <scheduler/Fps.h>
 #include <scheduler/FrameRateMode.h>
 #include "DisplayHardware/HWC2.h"
 #include "FpsOps.h"
@@ -35,6 +36,9 @@
 
 #include "libsurfaceflinger_unittest_main.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+using namespace com::android::graphics::surfaceflinger;
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
@@ -47,6 +51,7 @@
 using SetPolicyResult = RefreshRateSelector::SetPolicyResult;
 
 using mock::createDisplayMode;
+using mock::createVrrDisplayMode;
 
 struct TestableRefreshRateSelector : RefreshRateSelector {
     using RefreshRateSelector::FrameRateRanking;
@@ -112,9 +117,9 @@
         return std::make_pair(ranking, consideredSignals);
     }
 
-    ftl::NonNull<DisplayModePtr> getBestFrameRateMode(
-            const std::vector<LayerRequirement>& layers = {}, GlobalSignals signals = {}) const {
-        return getRankedFrameRates(layers, signals).ranking.front().frameRateMode.modePtr;
+    FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
+                                       GlobalSignals signals = {}) const {
+        return getRankedFrameRates(layers, signals).ranking.front().frameRateMode;
     }
 
     ScoredFrameRate getBestScoredFrameRate(const std::vector<LayerRequirement>& layers = {},
@@ -200,6 +205,19 @@
     static inline const ftl::NonNull<DisplayModePtr> kMode10 =
             ftl::as_non_null(createDisplayMode(kModeId10, 10_Hz));
 
+    // VRR modes
+    static inline const ftl::NonNull<DisplayModePtr> kVrrMode120TE240 = ftl::as_non_null(
+            createVrrDisplayMode(kModeId120, 240_Hz,
+                                 hal::VrrConfig{
+                                         .minFrameIntervalNs =
+                                                 static_cast<Fps>(120_Hz).getPeriodNsecs()}));
+
+    static inline const ftl::NonNull<DisplayModePtr> kVrrMode60TE120 = ftl::as_non_null(
+            createVrrDisplayMode(kModeId60, 120_Hz,
+                                 hal::VrrConfig{.minFrameIntervalNs =
+                                                        static_cast<Fps>(60_Hz).getPeriodNsecs()},
+                                 /*group=*/1));
+
     // Test configurations.
     static inline const DisplayModes kModes_60 = makeModes(kMode60);
     static inline const DisplayModes kModes_35_60_90 = makeModes(kMode35, kMode60, kMode90);
@@ -224,6 +242,11 @@
     static inline const DisplayModes kModes_1_5_10 = makeModes(kMode1, kMode5, kMode10);
     static inline const DisplayModes kModes_60_90_120 = makeModes(kMode60, kMode90, kMode120);
 
+    // VRR display modes
+    static inline const DisplayModes kVrrMode_120 = makeModes(kVrrMode120TE240);
+    static inline const DisplayModes kVrrModes_60_120 =
+            makeModes(kVrrMode60TE120, kVrrMode120TE240);
+
     // This is a typical TV configuration.
     static inline const DisplayModes kModes_24_25_30_50_60_Frac =
             makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
@@ -406,11 +429,11 @@
 
         // If there are no layers we select the default frame rate, which is the max of the primary
         // range.
-        EXPECT_EQ(kMode90, selector.getBestFrameRateMode());
+        EXPECT_EQ(kMode90, selector.getBestFrameRateMode().modePtr);
 
         EXPECT_EQ(SetPolicyResult::Changed,
                   selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-        EXPECT_EQ(kMode60, selector.getBestFrameRateMode());
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode().modePtr);
     }
     {
         // We select max even when this will cause a non-seamless switch.
@@ -419,7 +442,7 @@
         EXPECT_EQ(SetPolicyResult::Changed,
                   selector.setDisplayManagerPolicy(
                           {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
-        EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode());
+        EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode().modePtr);
     }
 }
 
@@ -432,7 +455,7 @@
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_90) {
@@ -443,107 +466,107 @@
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.name = "";
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multipleThreshold_60_90) {
@@ -554,32 +577,32 @@
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_72_90) {
@@ -589,26 +612,26 @@
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90_120) {
@@ -622,19 +645,19 @@
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_90_120_DifferentTypes) {
@@ -650,7 +673,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -658,7 +681,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -666,7 +689,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -674,7 +697,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -682,7 +705,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -690,7 +713,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -698,7 +721,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -706,7 +729,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -714,7 +737,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest,
@@ -733,7 +756,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -741,7 +764,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -749,7 +772,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -757,7 +780,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -765,7 +788,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -773,7 +796,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -781,7 +804,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -789,7 +812,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -797,14 +820,14 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -812,7 +835,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "120Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -820,7 +843,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::ExplicitExact;
     lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 10_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -828,7 +851,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 30_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -839,7 +862,7 @@
     lr3.vote = LayerVoteType::Heuristic;
     lr3.desiredRefreshRate = 120_Hz;
     lr3.name = "120Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60) {
@@ -849,26 +872,26 @@
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90) {
@@ -879,42 +902,42 @@
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_PriorityTest) {
@@ -926,39 +949,39 @@
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 15_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 30_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo) {
@@ -970,9 +993,10 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestFrameRateMode(layers);
+        const auto mode = selector.getBestFrameRateMode(layers).modePtr;
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
+                                 << to_string(mode->getPeakFps()) << "("
+                                 << to_string(mode->getVsyncRate()) << ")";
     }
 }
 
@@ -985,9 +1009,10 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestFrameRateMode(layers);
+        const auto mode = selector.getBestFrameRateMode(layers).modePtr;
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
+                                 << to_string(mode->getPeakFps()) << "("
+                                 << to_string(mode->getVsyncRate()) << ")";
     }
 }
 
@@ -1002,19 +1027,19 @@
     lr1.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_75HzContent) {
@@ -1026,9 +1051,10 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestFrameRateMode(layers, {});
+        const auto mode = selector.getBestFrameRateMode(layers, {}).modePtr;
         EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
+                                 << to_string(mode->getPeakFps()) << "("
+                                 << to_string(mode->getVsyncRate()) << ")";
     }
 }
 
@@ -1045,7 +1071,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
@@ -1053,14 +1079,14 @@
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
@@ -1068,14 +1094,14 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
@@ -1090,28 +1116,28 @@
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1120,7 +1146,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
@@ -1145,9 +1171,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1174,9 +1200,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1206,9 +1232,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1238,9 +1264,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1266,9 +1292,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
@@ -1280,9 +1306,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1300,9 +1326,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
@@ -1325,9 +1351,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1381,6 +1407,269 @@
     EXPECT_FALSE(signals.touch);
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_30_60_90_120) {
+    auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<Case> testCases = {
+            // Cases that only have frame rate category requirements, but no desired frame rate.
+            // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+            {0_Hz, FrameRateCategory::High, 90_Hz},
+            {0_Hz, FrameRateCategory::Normal, 60_Hz},
+            {0_Hz, FrameRateCategory::Low, 30_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+            // Cases that have both desired frame rate and frame rate category requirements.
+            {24_Hz, FrameRateCategory::High, 120_Hz},
+            {30_Hz, FrameRateCategory::High, 90_Hz},
+            {12_Hz, FrameRateCategory::Normal, 60_Hz},
+            {30_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+            // Cases that only have desired frame rate.
+            {30_Hz, FrameRateCategory::Default, 30_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+              to_string(testCase.desiredFrameRate).c_str(),
+              ftl::enum_string(testCase.frameRateCategory).c_str());
+
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate,
+                  selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<Case> testCases = {
+            // Cases that only have frame rate category requirements, but no desired frame rate.
+            // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+            {0_Hz, FrameRateCategory::High, 120_Hz},
+            {0_Hz, FrameRateCategory::Normal, 60_Hz},
+            {0_Hz, FrameRateCategory::Low, 60_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
+
+            // Cases that have both desired frame rate and frame rate category requirements.
+            {24_Hz, FrameRateCategory::High, 120_Hz},
+            {30_Hz, FrameRateCategory::High, 120_Hz},
+            {12_Hz, FrameRateCategory::Normal, 60_Hz},
+            {30_Hz, FrameRateCategory::NoPreference, 60_Hz},
+
+            // Cases that only have desired frame rate.
+            {30_Hz, FrameRateCategory::Default, 60_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+              to_string(testCase.desiredFrameRate).c_str(),
+              ftl::enum_string(testCase.frameRateCategory).c_str());
+
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate,
+                  selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+
+    struct Case {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        bool smoothSwitchOnly = false;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId60;
+    };
+
+    const std::initializer_list<Case> testCases = {
+            // These layers may switch modes because smoothSwitchOnly=false.
+            {FrameRateCategory::Default, false, 120_Hz, kModeId120},
+            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+            {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
+            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
+            {FrameRateCategory::High, false, 120_Hz, kModeId120},
+
+            // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
+            // active mode (120Hz).
+            {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
+            {FrameRateCategory::High, true, 120_Hz, kModeId120},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing frameRateCategory=%s (smooth=%d)", __func__,
+              ftl::enum_string(testCase.frameRateCategory).c_str(), testCase.smoothSwitchOnly);
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory)
+               << " smooth:" << testCase.smoothSwitchOnly << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .frameRateCategorySmoothSwitchOnly =
+                                              testCase.smoothSwitchOnly,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(testCase.expectedFrameRate, actualFrameRateMode.fps)
+                << "Did not get expected frame rate for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+
+        EXPECT_EQ(testCase.expectedModeId, actualFrameRateMode.modePtr->getId())
+                << "Did not get expected mode for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_vrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
+    auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+    struct Case {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        bool smoothSwitchOnly = false;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Note that `smoothSwitchOnly` should not have an effect.
+    const std::initializer_list<Case> testCases = {
+            {FrameRateCategory::Default, false, 240_Hz},
+            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+            {FrameRateCategory::NoPreference, false, 240_Hz},
+            {FrameRateCategory::Low, false, 30_Hz},
+            {FrameRateCategory::Normal, false, 60_Hz},
+            {FrameRateCategory::High, false, 120_Hz},
+            {FrameRateCategory::Default, true, 240_Hz},
+            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+            {FrameRateCategory::NoPreference, true, 240_Hz},
+            {FrameRateCategory::Low, true, 30_Hz},
+            {FrameRateCategory::Normal, true, 60_Hz},
+            {FrameRateCategory::High, true, 120_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing frameRateCategory=%s (smooth=%d)", __func__,
+              ftl::enum_string(testCase.frameRateCategory).c_str(), testCase.smoothSwitchOnly);
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory)
+               << " smooth:" << testCase.smoothSwitchOnly << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .frameRateCategorySmoothSwitchOnly =
+                                              testCase.smoothSwitchOnly,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(testCase.expectedFrameRate, actualFrameRateMode.fps)
+                << "Did not get expected frame rate for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+
+        // Expect all cases to be able to stay at the mode with TE 240 due to VRR compatibility.
+        EXPECT_EQ(kVrrMode120TE240->getId(), actualFrameRateMode.modePtr->getId())
+                << "Did not get expected mode for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+    }
+}
+
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitDefault) {
     auto selector = createSelector(kModes_60_90_72_120, kModeId60);
 
@@ -1414,9 +1703,10 @@
         ss << "ExplicitDefault " << desired;
         lr.name = ss.str();
 
-        const auto bestFps = selector.getBestFrameRateMode(layers)->getFps();
-        EXPECT_EQ(expected, bestFps)
-                << "expected " << expected << " for " << desired << " but got " << bestFps;
+        const auto bestMode = selector.getBestFrameRateMode(layers).modePtr;
+        EXPECT_EQ(expected, bestMode->getPeakFps())
+                << "expected " << expected << " for " << desired << " but got "
+                << bestMode->getPeakFps() << "(" << bestMode->getVsyncRate() << ")";
     }
 }
 
@@ -1434,7 +1724,7 @@
         lr.vote = LayerVoteType::ExplicitExactOrMultiple;
         lr.desiredRefreshRate = 23.976_Hz;
         lr.name = "ExplicitExactOrMultiple 23.976 Hz";
-        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 24 will choose 23.976 if 24 is not supported
@@ -1445,7 +1735,7 @@
 
         lr.desiredRefreshRate = 24_Hz;
         lr.name = "ExplicitExactOrMultiple 24 Hz";
-        EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 29.97 will prefer 59.94 over 60 and 30
@@ -1456,7 +1746,7 @@
 
         lr.desiredRefreshRate = 29.97_Hz;
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 29.97 will choose 60 if 59.94 is not supported
@@ -1465,7 +1755,7 @@
 
         lr.desiredRefreshRate = 29.97_Hz;
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 59.94 will choose 60 if 59.94 is not supported
@@ -1474,7 +1764,7 @@
 
         lr.desiredRefreshRate = 59.94_Hz;
         lr.name = "ExplicitExactOrMultiple 59.94 Hz";
-        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 }
 
@@ -1493,7 +1783,8 @@
             ss << "ExplicitExact " << desired;
             lr.name = ss.str();
 
-            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getFps());
+            EXPECT_EQ(lr.desiredRefreshRate,
+                      selector.getBestFrameRateMode(layers).modePtr->getPeakFps());
         }
     }
 }
@@ -1539,7 +1830,7 @@
     lr.desiredRefreshRate = 90_Hz;
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) {
@@ -1598,9 +1889,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Max;
@@ -1640,9 +1931,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Heuristic;
@@ -1680,9 +1971,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     lr1.desiredRefreshRate = 120_Hz;
@@ -1723,9 +2014,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1750,46 +2041,46 @@
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::ExplicitDefault;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Heuristic;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Max";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Min;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Min";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
@@ -1805,7 +2096,7 @@
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
@@ -1823,7 +2114,7 @@
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
@@ -1842,7 +2133,7 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
@@ -1863,7 +2154,7 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
@@ -1887,7 +2178,7 @@
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
@@ -1916,7 +2207,7 @@
     layers[1].name = "90Hz ExplicitDefault";
     layers[1].focused = false;
 
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
@@ -1949,7 +2240,7 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
@@ -1979,7 +2270,7 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
@@ -1999,10 +2290,10 @@
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 
     selector.setActiveMode(kModeId120, 120_Hz);
-    EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
@@ -2027,14 +2318,14 @@
                                              .weight = 1.f,
                                              .focused = true}};
 
-    EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers).modePtr->getId());
 
     auto& seamedLayer = layers[0];
     seamedLayer.desiredRefreshRate = 30_Hz;
     seamedLayer.name = "30Hz ExplicitDefault";
     selector.setActiveMode(kModeId30, 30_Hz);
 
-    EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
@@ -2049,7 +2340,7 @@
     std::vector<LayerRequirement> layers = {
             {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
 
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
@@ -2070,7 +2361,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
         layers[0].focused = args.focused;
-        return selector.getBestFrameRateMode(layers, {.touch = args.touch})->getId();
+        return selector.getBestFrameRateMode(layers, {.touch = args.touch}).modePtr->getId();
     };
 
     constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
@@ -2079,7 +2370,7 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode()->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode().modePtr->getId());
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
     EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
@@ -2147,7 +2438,8 @@
     }
 
     // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+    EXPECT_EQ(kModeId90,
+              selector.getBestFrameRateMode({}, {.touch = true, .idle = true}).modePtr->getId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
     selector.setActiveMode(kModeId90, 90_Hz);
@@ -2164,7 +2456,7 @@
     }
 
     // Idle should be applied rather than the active mode when there are no layers.
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true}).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, findClosestKnownFrameRate) {
@@ -2213,7 +2505,7 @@
 
     for (const auto& [fps, mode] : knownFrameRatesExpectations) {
         layer.desiredRefreshRate = fps;
-        EXPECT_EQ(mode, selector.getBestFrameRateMode(layers));
+        EXPECT_EQ(mode, selector.getBestFrameRateMode(layers).modePtr);
     }
 }
 
@@ -2316,17 +2608,17 @@
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
     if (GetParam() == Config::FrameRateOverride::Disabled) {
-        EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
     } else {
-        EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}));
+        EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
     }
 
     explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
 
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_ExactAndDefault) {
@@ -2344,7 +2636,7 @@
     explicitDefaultLayer.name = "ExplicitDefault";
     explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
 
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 // b/190578904
@@ -2370,7 +2662,8 @@
     const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
         layers[0].desiredRefreshRate = fps;
         layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
+        EXPECT_EQ(fps.getIntValue(),
+                  selector.getBestFrameRateMode(layers).modePtr->getPeakFps().getIntValue())
                 << "Failed for " << ftl::enum_string(vote);
     };
 
@@ -2409,13 +2702,13 @@
             },
     };
 
-    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getFps());
+    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals).modePtr->getPeakFps());
 }
 
 TEST_P(RefreshRateSelectorTest, modeComparison) {
-    EXPECT_LT(kMode60->getFps(), kMode90->getFps());
-    EXPECT_GE(kMode60->getFps(), kMode60->getFps());
-    EXPECT_GE(kMode90->getFps(), kMode90->getFps());
+    EXPECT_LT(kMode60->getPeakFps(), kMode90->getPeakFps());
+    EXPECT_GE(kMode60->getPeakFps(), kMode60->getPeakFps());
+    EXPECT_GE(kMode90->getPeakFps(), kMode90->getPeakFps());
 }
 
 TEST_P(RefreshRateSelectorTest, testKernelIdleTimerAction) {
@@ -2464,27 +2757,27 @@
     auto selector = createSelector(kModes_30_60_72_90_120, kModeId30);
 
     const auto frameRate = 30_Hz;
-    Fps displayRefreshRate = selector.getActiveMode().getFps();
+    Fps displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(1, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId60, 60_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(2, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId72, 72_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId90, 90_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(3, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId120, 120_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId90, 90_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
 
     EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 25_Hz));
@@ -2914,7 +3207,7 @@
     for (size_t i = 0; i < expected.size(); i++) {
         const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
         EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps);
-        EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getFps());
+        EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getPeakFps());
     }
 }
 
@@ -3020,7 +3313,8 @@
               selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 90_Hz}}));
 
     // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+    EXPECT_EQ(kModeId90,
+              selector.getBestFrameRateMode({}, {.touch = true, .idle = true}).modePtr->getId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
     selector.setActiveMode(kModeId90, 90_Hz);
@@ -3036,7 +3330,7 @@
     }
 
     // Idle should be applied rather than the active mode when there are no layers.
-    EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+    EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true}).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, policyCanBeInfinity) {
@@ -3060,6 +3354,7 @@
 }
 
 // TODO(b/266481656): Once this bug is fixed, we can remove this test
+// And test for VRR when we remove this work around for VRR.
 TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
@@ -3189,5 +3484,187 @@
     EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
 }
 
+// VRR tests
+TEST_P(RefreshRateSelectorTest, singleMinMaxRateForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    EXPECT_TRUE(selector.supportsFrameRateOverride());
+
+    const auto minRate = selector.getMinSupportedRefreshRate();
+    const auto performanceRate = selector.getMaxSupportedRefreshRate();
+    const auto minRateByPolicy = selector.getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kVrrMode120TE240, minRate);
+    EXPECT_EQ(kVrrMode120TE240, performanceRate);
+    EXPECT_EQ(kVrrMode120TE240, minRateByPolicy);
+    EXPECT_EQ(kVrrMode120TE240, performanceRateByPolicy);
+}
+
+TEST_P(RefreshRateSelectorTest, renderRateChangesWithPolicyChangeForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+    const FpsRange only120 = {120_Hz, 120_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, only120}, {only120, only120}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 120_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range120 = {0_Hz, 120_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range120}, {only120, range120}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 120_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range90 = {0_Hz, 90_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range90}, {only120, range90}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 80_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range80 = {0_Hz, 80_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range80}, {only120, range80}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 80_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range60 = {0_Hz, 60_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range60}, {only120, range60}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 60_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range48 = {0_Hz, 48_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range48}, {only120, range48}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 48_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range30 = {0_Hz, 30_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range30}, {only120, range30}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 30_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, modeChangesWithPolicyChangeForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+    const FpsRange range120 = {0_Hz, 120_Hz};
+    const FpsRange range60 = {0_Hz, 60_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {range120, range60}, {range120, range60}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 60_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId60, {range60, range60}, {range60, range60}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode60TE120, 60_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverridesForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    // TODO(b/297600226) Run at lower than 30 Fps for dVRR
+    const std::vector<Fps> desiredRefreshRates = {30_Hz, 34.285_Hz, 40_Hz, 48_Hz,
+                                                  60_Hz, 80_Hz,     120_Hz};
+    const std::vector<LayerVoteType> layerVotes = {LayerVoteType::ExplicitDefault,
+                                                   LayerVoteType::ExplicitExactOrMultiple,
+                                                   LayerVoteType::ExplicitExact};
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+
+    for (auto desiredRefreshRate : desiredRefreshRates) {
+        layers[0].desiredRefreshRate = desiredRefreshRate;
+        for (auto vote : layerVotes) {
+            layers[0].vote = vote;
+            auto frameRateOverrides = selector.getFrameRateOverrides(layers, 240_Hz, {});
+            EXPECT_EQ(1u, frameRateOverrides.size());
+            ASSERT_EQ(1u, frameRateOverrides.count(1234));
+            EXPECT_EQ(desiredRefreshRate, frameRateOverrides.at(1234));
+        }
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRatesForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    const FpsRange only120 = {120_Hz, 120_Hz};
+    const FpsRange range120 = {0_Hz, 120_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range120}, {only120, range120}}));
+
+    std::vector<Fps> expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz,
+                                 40_Hz, 48_Hz,     60_Hz, 80_Hz,     120_Hz};
+
+    auto primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+        EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+    }
+
+    // Render range (0,90)
+    const FpsRange range90 = {0_Hz, 90_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {range120, range90}, {range120, range90}}));
+
+    expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz, 40_Hz, 48_Hz, 60_Hz, 80_Hz};
+
+    primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+        EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+    }
+
+    // Render range (0,60)
+    const FpsRange range60 = {0_Hz, 60_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {range120, range60}, {range120, range60}}));
+    expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz, 40_Hz, 48_Hz, 60_Hz};
+
+    primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+        EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+    }
+}
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 682c998..87fae2c 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -30,6 +30,10 @@
 #include "mock/MockLayer.h"
 #include "mock/MockSchedulerCallback.h"
 
+#include <FrontEnd/LayerHierarchy.h>
+
+#include "FpsOps.h"
+
 namespace android::scheduler {
 
 using android::mock::createDisplayMode;
@@ -42,13 +46,16 @@
 using MockEventThread = android::mock::EventThread;
 using MockLayer = android::mock::MockLayer;
 
+using LayerHierarchy = surfaceflinger::frontend::LayerHierarchy;
+using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder;
+using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState;
+
 class SchedulerTest : public testing::Test {
 protected:
     class MockEventThreadConnection : public android::EventThreadConnection {
     public:
         explicit MockEventThreadConnection(EventThread* eventThread)
-              : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0),
-                                      ResyncCallback()) {}
+              : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0)) {}
         ~MockEventThreadConnection() = default;
 
         MOCK_METHOD1(stealReceiveChannel, binder::Status(gui::BitTube* outChannel));
@@ -83,6 +90,7 @@
 
     mock::SchedulerCallback mSchedulerCallback;
     TestableScheduler* mScheduler = new TestableScheduler{mSelector, mSchedulerCallback};
+    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
 
     ConnectionHandle mConnectionHandle;
     MockEventThread* mEventThread;
@@ -149,10 +157,6 @@
 
     EXPECT_CALL(*mEventThread, setDuration(10ns, 20ns)).Times(1);
     mScheduler->setDuration(mConnectionHandle, 10ns, 20ns);
-
-    static constexpr size_t kEventConnections = 5;
-    EXPECT_CALL(*mEventThread, getEventThreadConnectionCount()).WillOnce(Return(kEventConnections));
-    EXPECT_EQ(kEventConnections, mScheduler->getEventThreadConnectionCount(mConnectionHandle));
 }
 
 TEST_F(SchedulerTest, registerDisplay) FTL_FAKE_GUARD(kMainThreadContext) {
@@ -188,7 +192,7 @@
 
     // recordLayerHistory should be a noop
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
-    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0,
+    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
                                    LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
 
@@ -199,7 +203,8 @@
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
 
     EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0);
-    mScheduler->chooseRefreshRateForContent();
+    mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr,
+                                            /*updateAttachedChoreographer*/ false);
 }
 
 TEST_F(SchedulerTest, updateDisplayModes) {
@@ -213,7 +218,7 @@
                                                                       kDisplay1Mode60->getId()));
 
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
-    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0,
+    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
                                    LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1u, mScheduler->getNumActiveLayers());
 }
@@ -249,6 +254,11 @@
     EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 40ms));
 
     EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
+
+    const auto savedMinAcquiredBuffers = mFlinger.mutableMinAcquiredBuffers();
+    mFlinger.mutableMinAcquiredBuffers() = 2;
+    EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
+    mFlinger.mutableMinAcquiredBuffers() = savedMinAcquiredBuffers;
 }
 
 MATCHER(Is120Hz, "") {
@@ -263,7 +273,7 @@
     const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
     EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
 
-    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0,
+    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, systemTime(),
                                    LayerHistory::LayerUpdateType::Buffer);
 
     constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
@@ -273,11 +283,13 @@
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
 
     EXPECT_CALL(mSchedulerCallback, requestDisplayModes(Is120Hz())).Times(1);
-    mScheduler->chooseRefreshRateForContent();
+    mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr,
+                                            /*updateAttachedChoreographer*/ false);
 
     // No-op if layer requirements have not changed.
     EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0);
-    mScheduler->chooseRefreshRateForContent();
+    mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr,
+                                            /*updateAttachedChoreographer*/ false);
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
@@ -432,4 +444,401 @@
     }
 }
 
+TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) {
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
+
+    using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
+
+    struct Compositor final : ICompositor {
+        VsyncIds vsyncIds;
+        bool committed = true;
+
+        void configure() override {}
+
+        bool commit(PhysicalDisplayId, const scheduler::FrameTargets& targets) override {
+            vsyncIds.clear();
+
+            for (const auto& [id, target] : targets) {
+                vsyncIds.emplace_back(id, target->vsyncId());
+            }
+
+            return committed;
+        }
+
+        CompositeResultsPerDisplay composite(PhysicalDisplayId,
+                                             const scheduler::FrameTargeters&) override {
+            CompositeResultsPerDisplay results;
+
+            for (const auto& [id, _] : vsyncIds) {
+                results.try_emplace(id,
+                                    CompositeResult{.compositionCoverage =
+                                                            CompositionCoverage::Hwc});
+            }
+
+            return results;
+        }
+
+        void sample() override {}
+    } compositor;
+
+    mScheduler->doFrameSignal(compositor, VsyncId(42));
+
+    const auto makeVsyncIds = [](VsyncId vsyncId) -> VsyncIds {
+        return {{kDisplayId1, vsyncId}, {kDisplayId2, vsyncId}};
+    };
+
+    EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds);
+
+    compositor.committed = false;
+    mScheduler->doFrameSignal(compositor, VsyncId(43));
+
+    // FrameTargets should be updated despite the skipped commit.
+    EXPECT_EQ(makeVsyncIds(VsyncId(43)), compositor.vsyncIds);
+}
+
+class AttachedChoreographerTest : public SchedulerTest {
+protected:
+    void frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility, Fps displayFps,
+                               Fps expectedChoreographerFps);
+};
+
+TEST_F(AttachedChoreographerTest, registerSingle) {
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    const sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+    EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size());
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+    EXPECT_EQ(1u,
+              mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size());
+    EXPECT_FALSE(
+            mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate.isValid());
+}
+
+TEST_F(AttachedChoreographerTest, registerMultipleOnSameLayer) {
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+    const auto handle = layer->getHandle();
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2);
+
+    EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_))
+            .WillOnce(Return(0))
+            .WillOnce(Return(0));
+
+    const auto mockConnection1 = sp<MockEventThreadConnection>::make(mEventThread);
+    const auto mockConnection2 = sp<MockEventThreadConnection>::make(mEventThread);
+    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+            .WillOnce(Return(mockConnection1))
+            .WillOnce(Return(mockConnection2));
+
+    const sp<IDisplayEventConnection> connection1 =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, handle);
+    const sp<IDisplayEventConnection> connection2 =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, handle);
+
+    EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size());
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+    EXPECT_EQ(2u,
+              mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size());
+    EXPECT_FALSE(
+            mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate.isValid());
+}
+
+TEST_F(AttachedChoreographerTest, registerMultipleOnDifferentLayers) {
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+    const sp<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2);
+    const sp<IDisplayEventConnection> connection1 =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer1->getHandle());
+    const sp<IDisplayEventConnection> connection2 =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer2->getHandle());
+
+    EXPECT_EQ(2u, mScheduler->mutableAttachedChoreographers().size());
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer1->getSequence()));
+    EXPECT_EQ(1u,
+              mScheduler->mutableAttachedChoreographers()[layer1->getSequence()]
+                      .connections.size());
+    EXPECT_FALSE(
+            mScheduler->mutableAttachedChoreographers()[layer1->getSequence()].frameRate.isValid());
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer2->getSequence()));
+    EXPECT_EQ(1u,
+              mScheduler->mutableAttachedChoreographers()[layer2->getSequence()]
+                      .connections.size());
+    EXPECT_FALSE(
+            mScheduler->mutableAttachedChoreographers()[layer2->getSequence()].frameRate.isValid());
+}
+
+TEST_F(AttachedChoreographerTest, removedWhenConnectionIsGone) {
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+    EXPECT_EQ(1u,
+              mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size());
+
+    // The connection is used all over this test, so it is quite hard to release it from here.
+    // Instead, we just do a small shortcut.
+    {
+        EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
+        sp<MockEventThreadConnection> mockConnection =
+                sp<MockEventThreadConnection>::make(mEventThread);
+        mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.clear();
+        mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.emplace(
+                mockConnection);
+    }
+
+    RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+    LayerHierarchy hierarchy(&layerState);
+    mScheduler->updateAttachedChoreographers(hierarchy, 60_Hz);
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+}
+
+TEST_F(AttachedChoreographerTest, removedWhenLayerIsGone) {
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+    sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    const sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+    layer.clear();
+    mFlinger.mutableLayersPendingRemoval().clear();
+    EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+}
+
+void AttachedChoreographerTest::frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility,
+                                                      Fps displayFps,
+                                                      Fps expectedChoreographerFps) {
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+    RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+    LayerHierarchy hierarchy(&layerState);
+
+    layerState.frameRate = layerFps.getValue();
+    layerState.frameRateCompatibility = frameRateCompatibility;
+
+    mScheduler->updateAttachedChoreographers(hierarchy, displayFps);
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+    EXPECT_EQ(expectedChoreographerFps,
+              mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate);
+    EXPECT_EQ(expectedChoreographerFps, mEventThreadConnection->frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateDefault) {
+    Fps layerFps = 30_Hz;
+    int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+    Fps displayFps = 60_Hz;
+    Fps expectedChoreographerFps = 30_Hz;
+
+    frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+
+    layerFps = Fps::fromValue(32.7f);
+    frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateExact) {
+    Fps layerFps = 30_Hz;
+    int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_EXACT;
+    Fps displayFps = 60_Hz;
+    Fps expectedChoreographerFps = 30_Hz;
+
+    frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+
+    layerFps = Fps::fromValue(32.7f);
+    expectedChoreographerFps = {};
+    frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateExactOrMultiple) {
+    Fps layerFps = 30_Hz;
+    int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+    Fps displayFps = 60_Hz;
+    Fps expectedChoreographerFps = 30_Hz;
+
+    frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+
+    layerFps = Fps::fromValue(32.7f);
+    expectedChoreographerFps = {};
+    frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateParent) {
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+
+    RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+    LayerHierarchy parentHierarchy(&parentState);
+
+    RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+    LayerHierarchy hierarchy(&layerState);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached));
+
+    layerState.frameRate = (30_Hz).getValue();
+    layerState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence()));
+
+    EXPECT_EQ(30_Hz, mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateParent2Children) {
+    const sp<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+
+    RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+    LayerHierarchy parentHierarchy(&parentState);
+
+    RequestedLayerState layer1State(LayerCreationArgs(layer1->getSequence()));
+    LayerHierarchy layer1Hierarchy(&layer1State);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&layer1Hierarchy, LayerHierarchy::Variant::Attached));
+
+    RequestedLayerState layer2State(LayerCreationArgs(layer1->getSequence()));
+    LayerHierarchy layer2Hierarchy(&layer2State);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&layer2Hierarchy, LayerHierarchy::Variant::Attached));
+
+    layer1State.frameRate = (30_Hz).getValue();
+    layer1State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    layer2State.frameRate = (20_Hz).getValue();
+    layer2State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence()));
+
+    EXPECT_EQ(60_Hz, mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateParentConflictingChildren) {
+    const sp<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+
+    RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+    LayerHierarchy parentHierarchy(&parentState);
+
+    RequestedLayerState layer1State(LayerCreationArgs(layer1->getSequence()));
+    LayerHierarchy layer1Hierarchy(&layer1State);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&layer1Hierarchy, LayerHierarchy::Variant::Attached));
+
+    RequestedLayerState layer2State(LayerCreationArgs(layer1->getSequence()));
+    LayerHierarchy layer2Hierarchy(&layer2State);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&layer2Hierarchy, LayerHierarchy::Variant::Attached));
+
+    layer1State.frameRate = (30_Hz).getValue();
+    layer1State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    layer2State.frameRate = (25_Hz).getValue();
+    layer2State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence()));
+
+    EXPECT_EQ(Fps(), mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateChild) {
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+    RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+    LayerHierarchy parentHierarchy(&parentState);
+
+    RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+    LayerHierarchy hierarchy(&layerState);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached));
+
+    parentState.frameRate = (30_Hz).getValue();
+    parentState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+
+    EXPECT_EQ(30_Hz, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateChildNotOverriddenByParent) {
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+    EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+    sp<IDisplayEventConnection> connection =
+            mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+    RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+    LayerHierarchy parentHierarchy(&parentState);
+
+    RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+    LayerHierarchy hierarchy(&layerState);
+    parentHierarchy.mChildren.push_back(
+            std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached));
+
+    parentState.frameRate = (30_Hz).getValue();
+    parentState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    layerState.frameRate = (60_Hz).getValue();
+    layerState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+    mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+    ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+
+    EXPECT_EQ(60_Hz, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate);
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 44ab569..9899d42 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -19,6 +19,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/LayerMetadata.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -85,8 +86,7 @@
 
 void SetFrameRateTest::commitTransaction() {
     for (auto layer : mLayers) {
-        auto c = layer->getDrawingState();
-        layer->commitTransaction(c);
+        layer->commitTransaction();
     }
 }
 
@@ -98,7 +98,7 @@
     const auto& layerFactory = GetParam();
 
     auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    layer->setFrameRate(FRAME_RATE_VOTE1);
+    layer->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
 }
@@ -115,13 +115,13 @@
     addChild(parent, child1);
     addChild(child1, child2);
 
-    child2->setFrameRate(FRAME_RATE_VOTE1);
+    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    child2->setFrameRate(FRAME_RATE_NO_VOTE);
+    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -140,27 +140,27 @@
     addChild(parent, child1);
     addChild(child1, child2);
 
-    child2->setFrameRate(FRAME_RATE_VOTE1);
-    child1->setFrameRate(FRAME_RATE_VOTE2);
-    parent->setFrameRate(FRAME_RATE_VOTE3);
+    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+    child1->setFrameRate(FRAME_RATE_VOTE2.vote);
+    parent->setFrameRate(FRAME_RATE_VOTE3.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    child2->setFrameRate(FRAME_RATE_NO_VOTE);
+    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE2, child2->getFrameRateForLayerTree());
 
-    child1->setFrameRate(FRAME_RATE_NO_VOTE);
+    child1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE3, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE3, child2->getFrameRateForLayerTree());
 
-    parent->setFrameRate(FRAME_RATE_NO_VOTE);
+    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -179,13 +179,13 @@
     addChild(parent, child1);
     addChild(child1, child2);
 
-    parent->setFrameRate(FRAME_RATE_VOTE1);
+    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    parent->setFrameRate(FRAME_RATE_NO_VOTE);
+    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -204,27 +204,27 @@
     addChild(parent, child1);
     addChild(child1, child2);
 
-    child2->setFrameRate(FRAME_RATE_VOTE1);
-    child1->setFrameRate(FRAME_RATE_VOTE2);
-    parent->setFrameRate(FRAME_RATE_VOTE3);
+    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+    child1->setFrameRate(FRAME_RATE_VOTE2.vote);
+    parent->setFrameRate(FRAME_RATE_VOTE3.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    parent->setFrameRate(FRAME_RATE_NO_VOTE);
+    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    child1->setFrameRate(FRAME_RATE_NO_VOTE);
+    child1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    child2->setFrameRate(FRAME_RATE_NO_VOTE);
+    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -242,7 +242,7 @@
 
     addChild(parent, child1);
 
-    parent->setFrameRate(FRAME_RATE_VOTE1);
+    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
@@ -254,7 +254,7 @@
     EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
 
-    parent->setFrameRate(FRAME_RATE_NO_VOTE);
+    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -273,7 +273,7 @@
     addChild(parent, child1);
     addChild(child1, child2);
 
-    parent->setFrameRate(FRAME_RATE_VOTE1);
+    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
@@ -285,7 +285,7 @@
     EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
 
-    parent->setFrameRate(FRAME_RATE_NO_VOTE);
+    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -306,14 +306,14 @@
     addChild(child1, child2);
     addChild(child1, child2_1);
 
-    child2->setFrameRate(FRAME_RATE_VOTE1);
+    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child2_1->getFrameRateForLayerTree());
 
-    child2->setFrameRate(FRAME_RATE_NO_VOTE);
+    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -326,48 +326,6 @@
                                          std::make_shared<EffectLayerFactory>()),
                          PrintToStringParamName);
 
-TEST_F(SetFrameRateTest, ValidateFrameRate) {
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Privileged APIs.
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    constexpr bool kPrivileged = true;
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
-                                  kPrivileged));
-    EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
-                                  kPrivileged));
-
-    // Invalid frame rate.
-    EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Invalid compatibility.
-    EXPECT_FALSE(
-            ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Invalid change frame rate strategy.
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, ""));
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, ""));
-}
-
 TEST_P(SetFrameRateTest, SetOnParentActivatesTree) {
     const auto& layerFactory = GetParam();
 
@@ -376,7 +334,7 @@
     auto child = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
     addChild(parent, child);
 
-    parent->setFrameRate(FRAME_RATE_VOTE1);
+    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
 
     auto& history = mFlinger.mutableScheduler().mutableLayerHistory();
@@ -389,8 +347,8 @@
     const auto summary = history.summarize(*selectorPtr, 0);
 
     ASSERT_EQ(2u, summary.size());
-    EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[0].desiredRefreshRate);
-    EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[1].desiredRefreshRate);
+    EXPECT_EQ(FRAME_RATE_VOTE1.vote.rate, summary[0].desiredRefreshRate);
+    EXPECT_EQ(FRAME_RATE_VOTE1.vote.rate, summary[1].desiredRefreshRate);
 }
 
 TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
@@ -406,7 +364,7 @@
     addChild(parent, child1);
     addChild(child1, childOfChild1);
 
-    childOfChild1->setFrameRate(FRAME_RATE_VOTE1);
+    childOfChild1->setFrameRate(FRAME_RATE_VOTE1.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
@@ -420,7 +378,7 @@
     EXPECT_EQ(FRAME_RATE_VOTE1, childOfChild1->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
 
-    childOfChild1->setFrameRate(FRAME_RATE_NO_VOTE);
+    childOfChild1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
     EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
diff --git a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
index b910485..05f9eed 100644
--- a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
@@ -23,38 +23,34 @@
 
 namespace android::scheduler {
 
-class SmallAreaDetectionMappingsAllowTest : public testing::Test {
+class SmallAreaDetectionAllowMappingsTest : public testing::Test {
 protected:
     SmallAreaDetectionAllowMappings mMappings;
+    static constexpr int32_t kAppId1 = 10100;
+    static constexpr int32_t kAppId2 = 10101;
+    static constexpr float kThreshold1 = 0.05f;
+    static constexpr float kThreshold2 = 0.07f;
 };
 
 namespace {
-TEST_F(SmallAreaDetectionMappingsAllowTest, testUpdate) {
-    const uid_t uid1 = 10100;
-    const uid_t uid2 = 10101;
-    const float threshold1 = 0.05f;
-    const float threshold2 = 0.07f;
-    std::vector<std::pair<uid_t, float>> mappings;
+TEST_F(SmallAreaDetectionAllowMappingsTest, testUpdate) {
+    std::vector<std::pair<int32_t, float>> mappings;
     mappings.reserve(2);
-    mappings.push_back(std::make_pair(uid1, threshold1));
-    mappings.push_back(std::make_pair(uid2, threshold2));
+    mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+    mappings.push_back(std::make_pair(kAppId2, kThreshold2));
 
     mMappings.update(mappings);
-    ASSERT_EQ(mMappings.getThresholdForUid(uid1).value(), threshold1);
-    ASSERT_EQ(mMappings.getThresholdForUid(uid2).value(), threshold2);
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1).value(), kThreshold1);
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId2).value(), kThreshold2);
 }
 
-TEST_F(SmallAreaDetectionMappingsAllowTest, testSetThesholdForUid) {
-    const uid_t uid = 10111;
-    const float threshold = 0.05f;
-
-    mMappings.setThesholdForUid(uid, threshold);
-    ASSERT_EQ(mMappings.getThresholdForUid(uid), threshold);
+TEST_F(SmallAreaDetectionAllowMappingsTest, testSetThesholdForAppId) {
+    mMappings.setThesholdForAppId(kAppId1, kThreshold1);
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), kThreshold1);
 }
 
-TEST_F(SmallAreaDetectionMappingsAllowTest, testUidNotInTheMappings) {
-    const uid_t uid = 10222;
-    ASSERT_EQ(mMappings.getThresholdForUid(uid), std::nullopt);
+TEST_F(SmallAreaDetectionAllowMappingsTest, testAppIdNotInTheMappings) {
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), std::nullopt);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 9ef3e9e..aeac80d 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -49,9 +49,17 @@
         mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
         mFlinger.configureAndCommit();
 
+        auto vsyncController = std::make_unique<mock::VsyncController>();
+        auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
+
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, currentPeriod())
+                .WillRepeatedly(Return(
+                        TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
                            .setRefreshRateSelector(std::move(selectorPtr))
-                           .inject();
+                           .inject(std::move(vsyncController), std::move(vsyncTracker));
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
         // will call setActiveConfig instead of setActiveConfigWithConstraints.
@@ -116,14 +124,12 @@
     EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*eventThread, createEventConnection(_, _))
             .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+                                                             mock::EventThread::kCallingUid)));
 
     EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
             .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+                                                             mock::EventThread::kCallingUid)));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
index 4e9f293..22b72f9 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
@@ -23,12 +23,12 @@
 #include "DisplayTransactionTestHelpers.h"
 #include "FakeDisplayInjector.h"
 
-#include <android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Boost.h>
 
 namespace android {
 namespace {
 
-using android::hardware::power::Boost;
+using aidl::android::hardware::power::Boost;
 
 TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) {
     using namespace std::chrono_literals;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index ffe8c10..3d1c900 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -39,12 +39,15 @@
     TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
                               std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr),
-                              /* modulatorPtr */ nullptr, callback) {}
+                              sp<VsyncModulator>::make(VsyncConfigSet{}), callback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
+          : Scheduler(*this, callback,
+                      (FeatureFlags)Feature::kContentDetection |
+                              Feature::kSmallDirtyContentDetection,
+                      std::move(modulatorPtr)) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
@@ -59,6 +62,12 @@
     MOCK_METHOD(void, scheduleFrame, (), (override));
     MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));
 
+    void doFrameSignal(ICompositor& compositor, VsyncId vsyncId) {
+        ftl::FakeGuard guard1(kMainThreadContext);
+        ftl::FakeGuard guard2(mDisplayLock);
+        Scheduler::onFrameSignal(compositor, vsyncId, TimePoint());
+    }
+
     // Used to inject mock event thread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
         return Scheduler::createConnection(std::move(eventThread));
@@ -104,6 +113,7 @@
 
     auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
     auto& mutableLayerHistory() { return mLayerHistory; }
+    auto& mutableAttachedChoreographers() { return mAttachedChoreographers; }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
@@ -168,6 +178,12 @@
                                           : VsyncSchedule::HwVsyncState::Disabled;
     }
 
+    void updateAttachedChoreographers(
+            const surfaceflinger::frontend::LayerHierarchy& layerHierarchy,
+            Fps displayRefreshRate) {
+        Scheduler::updateAttachedChoreographers(layerHierarchy, displayRefreshRate);
+    }
+
     using Scheduler::onHardwareVsyncRequest;
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 6b13c0d..8f1982d 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -55,6 +55,12 @@
 #include "mock/MockSchedulerCallback.h"
 #include "mock/system/window/MockNativeWindow.h"
 
+#include "Scheduler/VSyncTracker.h"
+#include "Scheduler/VsyncController.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+#include "mock/MockVsyncController.h"
+
 namespace android {
 
 struct DisplayStatInfo;
@@ -275,14 +281,12 @@
         EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*eventThread, createEventConnection(_, _))
                 .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                                 mock::EventThread::kCallingUid,
-                                                                 ResyncCallback())));
+                                                                 mock::EventThread::kCallingUid)));
 
         EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
                 .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                                 mock::EventThread::kCallingUid,
-                                                                 ResyncCallback())));
+                                                                 mock::EventThread::kCallingUid)));
 
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
@@ -600,6 +604,22 @@
         return static_cast<mock::FrameTracer*>(mFlinger->mFrameTracer.get());
     }
 
+    void injectLegacyLayer(sp<Layer> layer) {
+        mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer;
+    };
+
+    void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
+
+    auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
+    auto setDaltonizerType(ColorBlindnessType type) {
+        mFlinger->mDaltonizer.setType(type);
+        return mFlinger->updateColorMatrixLocked();
+    }
+    auto updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
+                              bool& out) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return mFlinger->updateLayerSnapshots(vsyncId, frameTimeNs, transactionsFlushed, out);
+    }
     /* ------------------------------------------------------------------------
      * Read-write access to private data to set up preconditions and assert
      * post-conditions.
@@ -625,7 +645,6 @@
     auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
     auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
-    auto& mutableTexturePool() { return mFlinger->mTexturePool; }
     auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; }
     auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; }
     auto& mutableMaxRenderTargetSize() { return mFlinger->mMaxRenderTargetSize; }
@@ -640,8 +659,22 @@
         return SurfaceFlinger::sActiveDisplayRotationFlags;
     }
 
+    auto& mutableMinAcquiredBuffers() { return SurfaceFlinger::minAcquiredBuffers; }
+    auto& mutableLayersPendingRemoval() { return mFlinger->mLayersPendingRemoval; }
+    auto& mutableLayerSnapshotBuilder() { return mFlinger->mLayerSnapshotBuilder; };
+
     auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
 
+    auto initTransactionTraceWriter() {
+        mFlinger->mTransactionTracing.emplace();
+        return mFlinger->initTransactionTraceWriter();
+    }
+
+    void enableNewFrontEnd() {
+        mFlinger->mLayerLifecycleManagerEnabled = true;
+        mFlinger->mLegacyFrontEndEnabled = false;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
@@ -650,10 +683,12 @@
         mutableDisplays().clear();
         mutableCurrentState().displays.clear();
         mutableDrawingState().displays.clear();
+        mFlinger->mLayersPendingRemoval.clear();
         mFlinger->mScheduler.reset();
         mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
         mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
         mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
+        mFlinger->mTransactionTracing.reset();
     }
 
     /* ------------------------------------------------------------------------
@@ -924,6 +959,13 @@
         }
 
         sp<DisplayDevice> inject() NO_THREAD_SAFETY_ANALYSIS {
+            return inject(std::make_unique<mock::VsyncController>(),
+                          std::make_shared<mock::VSyncTracker>());
+        }
+
+        sp<DisplayDevice> inject(std::unique_ptr<android::scheduler::VsyncController> controller,
+                                 std::shared_ptr<android::scheduler::VSyncTracker> tracker)
+                NO_THREAD_SAFETY_ANALYSIS {
             const auto displayId = mCreationArgs.compositionDisplay->getDisplayId();
 
             auto& modes = mDisplayModes;
@@ -974,7 +1016,7 @@
 
                 const auto activeMode = modes.get(activeModeId);
                 LOG_ALWAYS_FATAL_IF(!activeMode);
-                const auto fps = activeMode->get()->getFps();
+                const auto fps = activeMode->get()->getPeakFps();
 
                 state.physical = {.id = physicalId,
                                   .hwcDisplayId = *mHwcDisplayId,
@@ -988,7 +1030,9 @@
 
                 if (mFlinger.scheduler() && mRegisterDisplay) {
                     mFlinger.scheduler()->registerDisplay(physicalId,
-                                                          display->holdRefreshRateSelector());
+                                                          display->holdRefreshRateSelector(),
+                                                          std::move(controller),
+                                                          std::move(tracker));
                 }
 
                 display->setActiveMode(activeModeId, fps, fps);
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index a9ae1d3..86ed233 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -1079,7 +1079,6 @@
     constexpr size_t TOTAL_FRAMES = 5;
     constexpr size_t MISSED_FRAMES = 4;
     constexpr size_t CLIENT_COMPOSITION_FRAMES = 3;
-    constexpr size_t DISPLAY_EVENT_CONNECTIONS = 14;
     constexpr nsecs_t DISPLAY_DEADLINE_DELTA = 1'000'000;
     constexpr nsecs_t DISPLAY_PRESENT_JITTER = 2'000'000;
     constexpr nsecs_t APP_DEADLINE_DELTA = 3'000'000;
@@ -1100,7 +1099,6 @@
 
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
 
-    mTimeStats->recordDisplayEventConnectionCount(DISPLAY_EVENT_CONNECTIONS);
     mTimeStats->setPowerMode(PowerMode::ON);
     mTimeStats->recordFrameDuration(1000000, 3000000);
     mTimeStats->recordRenderEngineDuration(2000000, 4000000);
@@ -1157,7 +1155,6 @@
     EXPECT_EQ(atom.client_composition_frames(), CLIENT_COMPOSITION_FRAMES);
     // Display on millis is not checked.
     EXPECT_EQ(atom.animation_millis(), 2);
-    EXPECT_EQ(atom.event_connection_count(), DISPLAY_EVENT_CONNECTIONS);
     EXPECT_THAT(atom.frame_duration(), HistogramEq(buildExpectedHistogram({2}, {1})));
     EXPECT_THAT(atom.render_engine_timing(), HistogramEq(buildExpectedHistogram({1, 2}, {1, 1})));
     EXPECT_EQ(atom.total_timeline_frames(), 9);
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index afb8efb..1f2a1ed 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -15,7 +15,7 @@
  */
 
 #undef LOG_TAG
-#define LOG_TAG "CompositionTest"
+#define LOG_TAG "TransactionApplicationTest"
 
 #include <compositionengine/Display.h>
 #include <compositionengine/mock/DisplaySurface.h>
@@ -306,6 +306,47 @@
     ~FakeExternalTexture() = default;
 };
 
+TEST_F(TransactionApplicationTest, ApplyTokensUseDifferentQueues) {
+    auto applyToken1 = sp<BBinder>::make();
+    auto applyToken2 = sp<BBinder>::make();
+
+    // Transaction 1 has a buffer with an unfired fence. It should not be ready to be applied.
+    TransactionState transaction1;
+    transaction1.applyToken = applyToken1;
+    transaction1.id = 42069;
+    transaction1.states.emplace_back();
+    transaction1.states[0].state.what |= layer_state_t::eBufferChanged;
+    transaction1.states[0].state.bufferData =
+            std::make_shared<fake::BufferData>(/* bufferId */ 1, /* width */ 1, /* height */ 1,
+                                               /* pixelFormat */ 0, /* outUsage */ 0);
+    transaction1.states[0].externalTexture =
+            std::make_shared<FakeExternalTexture>(*transaction1.states[0].state.bufferData);
+    transaction1.states[0].state.surface =
+            sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
+                    ->getHandle();
+    auto fence = sp<mock::MockFence>::make();
+    EXPECT_CALL(*fence, getStatus()).WillRepeatedly(Return(Fence::Status::Unsignaled));
+    transaction1.states[0].state.bufferData->acquireFence = std::move(fence);
+    transaction1.states[0].state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
+    transaction1.isAutoTimestamp = true;
+
+    // Transaction 2 should be ready to be applied.
+    TransactionState transaction2;
+    transaction2.applyToken = applyToken2;
+    transaction2.id = 2;
+    transaction2.isAutoTimestamp = true;
+
+    mFlinger.setTransactionStateInternal(transaction1);
+    mFlinger.setTransactionStateInternal(transaction2);
+    mFlinger.flushTransactionQueues();
+    auto transactionQueues = mFlinger.getPendingTransactionQueue();
+
+    // Transaction 1 is still in its queue.
+    EXPECT_EQ(transactionQueues[applyToken1].size(), 1u);
+    // Transaction 2 has been dequeued.
+    EXPECT_EQ(transactionQueues[applyToken2].size(), 0u);
+}
+
 class LatchUnsignaledTest : public TransactionApplicationTest {
 public:
     void TearDown() override {
@@ -855,233 +896,13 @@
                          kExpectedTransactionsPending);
 }
 
-class LatchUnsignaledAlwaysTest : public LatchUnsignaledTest {
-public:
-    void SetUp() override {
-        LatchUnsignaledTest::SetUp();
-        SurfaceFlinger::enableLatchUnsignaledConfig = LatchUnsignaledConfig::Always;
-    }
-};
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId = 1;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto signaledTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {createComposerState(kLayerId, fence(Fence::Status::Signaled),
-                                                       layer_state_t::eBufferChanged)});
-    setTransactionStates({signaledTransaction}, kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueue) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId = 1;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto unsignaledTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {createComposerState(kLayerId, fence(Fence::Status::Unsignaled),
-                                                       layer_state_t::eBufferChanged)});
-    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueSameLayerId) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId = 1;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto mixedTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {createComposerState(kLayerId, fence(Fence::Status::Unsignaled),
-                                                       layer_state_t::eBufferChanged),
-                                   createComposerState(kLayerId, fence(Fence::Status::Signaled),
-                                                       layer_state_t::eBufferChanged)});
-    setTransactionStates({mixedTransaction}, kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentLayerId) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId1 = 1;
-    const auto kLayerId2 = 2;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto mixedTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {createComposerState(kLayerId1, fence(Fence::Status::Unsignaled),
-                                                       layer_state_t::eBufferChanged),
-                                   createComposerState(kLayerId2, fence(Fence::Status::Signaled),
-                                                       layer_state_t::eBufferChanged)});
-    setTransactionStates({mixedTransaction}, kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId1 = 1;
-    const auto kLayerId2 = 2;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto signaledTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {
-                                          createComposerState(kLayerId1,
-                                                              fence(Fence::Status::Signaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    const auto signaledTransaction2 =
-            createTransactionInfo(kApplyToken,
-                                  {
-                                          createComposerState(kLayerId2,
-                                                              fence(Fence::Status::Signaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentApplyToken) {
-    const sp<IBinder> kApplyToken1 =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
-    const auto kLayerId1 = 1;
-    const auto kLayerId2 = 2;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto signaledTransaction =
-            createTransactionInfo(kApplyToken1,
-                                  {
-                                          createComposerState(kLayerId1,
-                                                              fence(Fence::Status::Signaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    const auto unsignaledTransaction =
-            createTransactionInfo(kApplyToken2,
-                                  {
-                                          createComposerState(kLayerId2,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    setTransactionStates({signaledTransaction, unsignaledTransaction},
-                         kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueueSameApplyToken) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId1 = 1;
-    const auto kLayerId2 = 2;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto unsignaledTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {
-                                          createComposerState(kLayerId1,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    const auto signaledTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {
-                                          createComposerState(kLayerId2,
-                                                              fence(Fence::Status::Signaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    setTransactionStates({unsignaledTransaction, signaledTransaction},
-                         kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueue) {
-    const sp<IBinder> kApplyToken1 =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
-    const auto kLayerId1 = 1;
-    const auto kLayerId2 = 2;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto unsignaledTransaction =
-            createTransactionInfo(kApplyToken1,
-                                  {
-                                          createComposerState(kLayerId1,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    const auto unsignaledTransaction2 =
-            createTransactionInfo(kApplyToken2,
-                                  {
-                                          createComposerState(kLayerId2,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-    setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
-                         kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, RespectsBackPressureFlag) {
-    const sp<IBinder> kApplyToken1 =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
-    const auto kLayerId1 = 1;
-    const auto kExpectedTransactionsPending = 1u;
-    auto layer =
-            sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}));
-    auto layerHandle = layer->getHandle();
-    const auto setBackPressureFlagTransaction =
-            createTransactionInfo(kApplyToken1,
-                                  {createComposerState(kLayerId1, fence(Fence::Status::Unsignaled),
-                                                       layer_state_t::eBufferChanged |
-                                                               layer_state_t::eFlagsChanged,
-                                                       {layerHandle})});
-    setTransactionStates({setBackPressureFlagTransaction}, 0u);
-
-    const auto unsignaledTransaction =
-            createTransactionInfo(kApplyToken1,
-                                  {
-                                          createComposerState(kLayerId1,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged,
-                                                              {layerHandle}),
-                                  });
-    const auto unsignaledTransaction2 =
-            createTransactionInfo(kApplyToken1,
-                                  {
-                                          createComposerState(kLayerId1,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged,
-                                                              {layerHandle}),
-                                  });
-    setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
-                         kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) {
-    const sp<IBinder> kApplyToken =
-            IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId = 1;
-    const auto kExpectedTransactionsPending = 0u;
-
-    const auto unsignaledTransaction =
-            createTransactionInfo(kApplyToken,
-                                  {
-                                          createComposerState(kLayerId,
-                                                              fence(Fence::Status::Unsignaled),
-                                                              layer_state_t::eBufferChanged),
-                                  });
-
-    modulateVsync();
-    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
-}
-
 TEST(TransactionHandlerTest, QueueTransaction) {
     TransactionHandler handler;
     TransactionState transaction;
     transaction.applyToken = sp<BBinder>::make();
     transaction.id = 42;
     handler.queueTransaction(std::move(transaction));
+    handler.collectTransactions();
     std::vector<TransactionState> transactionsReadyToBeApplied = handler.flushTransactions();
 
     EXPECT_EQ(transactionsReadyToBeApplied.size(), 1u);
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index 764d19b..00b5bf0 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -61,10 +61,7 @@
         return sp<Layer>::make(args);
     }
 
-    void commitTransaction(Layer* layer) {
-        auto c = layer->getDrawingState();
-        layer->commitTransaction(c);
-    }
+    void commitTransaction(Layer* layer) { layer->commitTransaction(); }
 
     TestableSurfaceFlinger mFlinger;
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index a95a645..cbb597a 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -85,7 +85,7 @@
 
     TransactionProtoParser parser(std::make_unique<TestMapper>(displayHandle));
 
-    proto::TransactionState proto = parser.toProto(t1);
+    perfetto::protos::TransactionState proto = parser.toProto(t1);
     TransactionState t2 = parser.fromProto(proto);
 
     ASSERT_EQ(t1.originPid, t2.originPid);
@@ -119,7 +119,7 @@
     d1.transformHint = ui::Transform::ROT_90;
 
     const uint32_t layerStack = 2;
-    google::protobuf::RepeatedPtrField<proto::DisplayInfo> displayProtos;
+    google::protobuf::RepeatedPtrField<perfetto::protos::DisplayInfo> displayProtos;
     auto displayInfoProto = displayProtos.Add();
     *displayInfoProto = TransactionProtoParser::toProto(d1, layerStack);
     frontend::DisplayInfos displayInfos;
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index e2c6491..5046a86 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -60,10 +60,7 @@
         return sp<Layer>::make(args);
     }
 
-    void commitTransaction(Layer* layer) {
-        auto c = layer->getDrawingState();
-        layer->commitTransaction(c);
-    }
+    void commitTransaction(Layer* layer) { layer->commitTransaction(); }
 
     TestableSurfaceFlinger mFlinger;
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
@@ -350,7 +347,7 @@
         // Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
         // pendingJankClassifications.
         EXPECT_EQ(2u, layer->mPendingJankClassifications.size());
-        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
+        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                          /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         layer->releasePendingBuffer(25);
 
@@ -487,10 +484,10 @@
         // BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
         // Since we don't have access to DisplayFrame here, trigger an onPresent directly.
         for (auto& surfaceFrame : bufferlessSurfaceFrames) {
-            surfaceFrame->onPresent(20, JankType::None, 90_Hz,
+            surfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                     /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         }
-        presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
+        presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                                /*displayDeadlineDelta*/ 0,
                                                /*displayPresentDelta*/ 0);
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
new file mode 100644
index 0000000..379135e
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTraceWriterTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <filesystem>
+
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class TransactionTraceWriterTest : public testing::Test {
+protected:
+    std::string mFilename = "/data/local/tmp/testfile_transaction_trace.winscope";
+
+    void SetUp() { mFlinger.initTransactionTraceWriter(); }
+    void TearDown() { std::filesystem::remove(mFilename); }
+
+    void verifyTraceFile() {
+        std::fstream file(mFilename, std::ios::in);
+        ASSERT_TRUE(file.is_open());
+        std::string line;
+        char magicNumber[8];
+        file.read(magicNumber, 8);
+        EXPECT_EQ("\tTNXTRAC", std::string(magicNumber, magicNumber + 8));
+    }
+
+    TestableSurfaceFlinger mFlinger;
+};
+
+TEST_F(TransactionTraceWriterTest, canWriteToFile) {
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+    verifyTraceFile();
+}
+
+TEST_F(TransactionTraceWriterTest, canOverwriteFile) {
+    std::string testLine = "test";
+    {
+        std::ofstream file(mFilename, std::ios::out);
+        file << testLine;
+    }
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    verifyTraceFile();
+}
+
+TEST_F(TransactionTraceWriterTest, doNotOverwriteFile) {
+    std::string testLine = "test";
+    {
+        std::ofstream file(mFilename, std::ios::out);
+        file << testLine;
+    }
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+    {
+        std::fstream file(mFilename, std::ios::in);
+        ASSERT_TRUE(file.is_open());
+        std::string line;
+        std::getline(file, line);
+        EXPECT_EQ(line, testLine);
+    }
+}
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 809966f..7981224 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -25,7 +25,6 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/Update.h"
 #include "Tracing/LayerTracing.h"
-#include "Tracing/RingBuffer.h"
 #include "Tracing/TransactionTracing.h"
 
 using namespace android::surfaceflinger;
@@ -38,11 +37,11 @@
     TransactionTracing mTracing;
 
     void flush() { mTracing.flush(); }
-    proto::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
+    perfetto::protos::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
 
-    proto::TransactionTraceEntry bufferFront() {
+    perfetto::protos::TransactionTraceEntry bufferFront() {
         std::scoped_lock<std::mutex> lock(mTracing.mTraceLock);
-        proto::TransactionTraceEntry entry;
+        perfetto::protos::TransactionTraceEntry entry;
         entry.ParseFromString(mTracing.mBuffer.front());
         return entry;
     }
@@ -60,7 +59,7 @@
         flush();
     }
 
-    void verifyEntry(const proto::TransactionTraceEntry& actualProto,
+    void verifyEntry(const perfetto::protos::TransactionTraceEntry& actualProto,
                      const std::vector<TransactionState>& expectedTransactions,
                      int64_t expectedVsyncId) {
         EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId);
@@ -118,7 +117,7 @@
     mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false);
     flush();
 
-    proto::TransactionTraceFile proto = writeToProto();
+    perfetto::protos::TransactionTraceFile proto = writeToProto();
     ASSERT_EQ(proto.entry().size(), 2);
     verifyEntry(proto.entry(0), firstUpdate.transactions, firstTransactionSetVsyncId);
     verifyEntry(proto.entry(1), secondUpdate.transactions, secondTransactionSetVsyncId);
@@ -204,7 +203,7 @@
     while (bufferFront().vsync_id() <= VSYNC_ID_FIRST_LAYER_CHANGE) {
         queueAndCommitTransaction(++mVsyncId);
     }
-    proto::TransactionTraceFile proto = writeToProto();
+    perfetto::protos::TransactionTraceFile proto = writeToProto();
     // verify we can still retrieve the layer change from the first entry containing starting
     // states.
     EXPECT_GT(proto.entry().size(), 0);
@@ -222,7 +221,7 @@
     while (bufferFront().vsync_id() <= VSYNC_ID_SECOND_LAYER_CHANGE) {
         queueAndCommitTransaction(++mVsyncId);
     }
-    proto::TransactionTraceFile proto = writeToProto();
+    perfetto::protos::TransactionTraceFile proto = writeToProto();
     // verify starting states are updated correctly
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 41);
 }
@@ -232,7 +231,7 @@
     while (bufferFront().vsync_id() <= VSYNC_ID_CHILD_LAYER_REMOVED) {
         queueAndCommitTransaction(++mVsyncId);
     }
-    proto::TransactionTraceFile proto = writeToProto();
+    perfetto::protos::TransactionTraceFile proto = writeToProto();
     // verify the child layer has been removed from the trace
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
@@ -243,7 +242,7 @@
     while (bufferFront().vsync_id() <= VSYNC_ID_SECOND_LAYER_CHANGE) {
         queueAndCommitTransaction(++mVsyncId);
     }
-    proto::TransactionTraceFile proto = writeToProto();
+    perfetto::protos::TransactionTraceFile proto = writeToProto();
     // verify we have two starting states
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
 
@@ -303,7 +302,7 @@
 };
 
 TEST_F(TransactionTracingMirrorLayerTest, canAddMirrorLayers) {
-    proto::TransactionTraceFile proto = writeToProto();
+    perfetto::protos::TransactionTraceFile proto = writeToProto();
     // We don't have any starting states since no layer was removed from.
     EXPECT_EQ(proto.entry().size(), 1);
 
@@ -318,18 +317,18 @@
 // Verify we can write the layers traces by entry to reduce mem pressure
 // on the system when generating large traces.
 TEST(LayerTraceTest, canStreamLayersTrace) {
-    LayersTraceFileProto inProto = LayerTracing::createTraceFileProto();
+    perfetto::protos::LayersTraceFileProto inProto = LayerTracing::createTraceFileProto();
     inProto.add_entry();
     inProto.add_entry();
 
     std::string output;
     inProto.SerializeToString(&output);
-    LayersTraceFileProto inProto2 = LayerTracing::createTraceFileProto();
+    perfetto::protos::LayersTraceFileProto inProto2 = LayerTracing::createTraceFileProto();
     inProto2.add_entry();
     std::string output2;
     inProto2.SerializeToString(&output2);
 
-    LayersTraceFileProto outProto;
+    perfetto::protos::LayersTraceFileProto outProto;
     outProto.ParseFromString(output + output2);
     // magic?
     EXPECT_EQ(outProto.entry().size(), 3);
diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
index 108151e..1cf14ae 100644
--- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
@@ -127,7 +127,7 @@
     sp<NativeHandle> stream =
             NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                  false);
-    layer->setSidebandStream(stream);
+    layer->setSidebandStream(stream, FrameTimelineInfo{}, 20);
     mFlinger.mutableCurrentState().layersSortedByZ.add(layer);
     mTunnelModeEnabledReporter->updateTunnelModeStatus();
     mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
@@ -151,7 +151,7 @@
     sp<NativeHandle> stream =
             NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                  false);
-    layerWithSidebandStream->setSidebandStream(stream);
+    layerWithSidebandStream->setSidebandStream(stream, FrameTimelineInfo{}, 20);
 
     mFlinger.mutableCurrentState().layersSortedByZ.add(simpleLayer);
     mFlinger.mutableCurrentState().layersSortedByZ.add(layerWithSidebandStream);
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 7af1da6..b8fdce1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -30,13 +30,17 @@
 
 #include <scheduler/TimeKeeper.h>
 
+#include "FlagUtils.h"
 #include "Scheduler/VSyncDispatchTimerQueue.h"
 #include "Scheduler/VSyncTracker.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 using namespace testing;
 using namespace std::literals;
 
 namespace android::scheduler {
+using namespace com::android::graphics::surfaceflinger;
 
 class MockVSyncTracker : public VSyncTracker {
 public:
@@ -1061,6 +1065,48 @@
     EXPECT_THAT(cb.mReadyTime[0], Eq(2000));
 }
 
+TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
+    EXPECT_CALL(mMockClock, alarmAt(_, 500));
+    CountingCallback cb(mDispatch);
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
+    mMockClock.advanceBy(300);
+
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1200, *result);
+
+    advanceToNextCallback();
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    EXPECT_CALL(mMockClock, alarmAt(_, 500));
+    CountingCallback cb(mDispatch);
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
+    mMockClock.advanceBy(300);
+
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(200, *result);
+
+    advanceToNextCallback();
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+}
+
 class VSyncDispatchTimerQueueEntryTest : public testing::Test {
 protected:
     nsecs_t const mPeriod = 1000;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index d3fb9fc..95004a4 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -51,6 +51,7 @@
     ~Composer() override;
 
     MOCK_METHOD(bool, isSupported, (OptionalFeature), (const, override));
+    MOCK_METHOD(bool, getDisplayConfigurationsSupported, (), (const, override));
     MOCK_METHOD0(getCapabilities,
                  std::vector<aidl::android::hardware::graphics::composer3::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
@@ -70,6 +71,8 @@
     MOCK_METHOD4(getDisplayAttribute,
                  Error(Display, Config config, IComposerClient::Attribute, int32_t*));
     MOCK_METHOD2(getDisplayConfigs, Error(Display, std::vector<Config>*));
+    MOCK_METHOD3(getDisplayConfigurations,
+                 Error(Display, int32_t, std::vector<DisplayConfiguration>*));
     MOCK_METHOD2(getDisplayName, Error(Display, std::string*));
     MOCK_METHOD4(getDisplayRequests,
                  Error(Display, uint32_t*, std::vector<Layer>*, std::vector<uint32_t>*));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index 3b36361..cb05c00 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -20,17 +20,23 @@
 
 namespace android::mock {
 
-inline DisplayModePtr createDisplayMode(
-        DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+inline DisplayMode::Builder createDisplayModeBuilder(
+        DisplayModeId modeId, Fps displayRefreshRate, int32_t group = 0,
         ui::Size resolution = ui::Size(1920, 1080),
         PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
     return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
             .setId(modeId)
             .setPhysicalDisplayId(displayId)
-            .setVsyncPeriod(refreshRate.getPeriodNsecs())
+            .setVsyncPeriod(displayRefreshRate.getPeriodNsecs())
             .setGroup(group)
-            .setResolution(resolution)
-            .build();
+            .setResolution(resolution);
+}
+
+inline DisplayModePtr createDisplayMode(
+        DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+        ui::Size resolution = ui::Size(1920, 1080),
+        PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+    return createDisplayModeBuilder(modeId, refreshRate, group, resolution, displayId).build();
 }
 
 inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
@@ -38,11 +44,19 @@
     return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
 }
 
+inline DisplayModePtr createVrrDisplayMode(
+        DisplayModeId modeId, Fps displayRefreshRate, hal::VrrConfig vrrConfig, int32_t group = 0,
+        ui::Size resolution = ui::Size(1920, 1080),
+        PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+    return createDisplayModeBuilder(modeId, displayRefreshRate, group, resolution, displayId)
+            .setVrrConfig(std::move(vrrConfig))
+            .build();
+}
 inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) {
     return DisplayMode::Builder(modePtr->getHwcId())
             .setId(modePtr->getId())
             .setPhysicalDisplayId(displayId)
-            .setVsyncPeriod(modePtr->getVsyncPeriod())
+            .setVsyncPeriod(modePtr->getVsyncRate().getPeriodNsecs())
             .setGroup(modePtr->getGroup())
             .setResolution(modePtr->getResolution())
             .build();
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
index 0ddc90d..a088aab 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -18,14 +18,14 @@
 
 #include "binder/Status.h"
 
-#include <android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPower.h>
 #include <gmock/gmock.h>
 
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPower;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
 using android::binder::Status;
-using android::hardware::power::Boost;
-using android::hardware::power::IPower;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
 
 namespace android::Hwc2::mock {
 
@@ -33,18 +33,19 @@
 public:
     MockIPower();
 
-    MOCK_METHOD(Status, isBoostSupported, (Boost boost, bool* ret), (override));
-    MOCK_METHOD(Status, setBoost, (Boost boost, int32_t durationMs), (override));
-    MOCK_METHOD(Status, isModeSupported, (Mode mode, bool* ret), (override));
-    MOCK_METHOD(Status, setMode, (Mode mode, bool enabled), (override));
-    MOCK_METHOD(Status, createHintSession,
+    MOCK_METHOD(ndk::ScopedAStatus, isBoostSupported, (Boost boost, bool* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setBoost, (Boost boost, int32_t durationMs), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, isModeSupported, (Mode mode, bool* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setMode, (Mode mode, bool enabled), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSession,
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                 int64_t durationNanos, sp<IPowerHintSession>* session),
+                 int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
-    MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * rate), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
 };
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index f4ded21..364618d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -18,14 +18,15 @@
 
 #include "binder/Status.h"
 
-#include <android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPower.h>
 #include <gmock/gmock.h>
 
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
 using android::binder::Status;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::SessionHint;
 
-using namespace android::hardware::power;
+using namespace aidl::android::hardware::power;
 
 namespace android::Hwc2::mock {
 
@@ -33,16 +34,19 @@
 public:
     MockIPowerHintSession();
 
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
-    MOCK_METHOD(Status, pause, (), (override));
-    MOCK_METHOD(Status, resume, (), (override));
-    MOCK_METHOD(Status, close, (), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override));
-    MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override));
-    MOCK_METHOD(Status, sendHint, (SessionHint), (override));
-    MOCK_METHOD(Status, setThreads, (const ::std::vector<int32_t>&), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, pause, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, resume, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, close, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, reportActualWorkDuration, (const ::std::vector<WorkDuration>&),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
 };
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index 8e22f43..68fe3c5 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -31,8 +31,8 @@
 
 namespace android::Hwc2::mock {
 
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using android::power::HalResult;
 
 class MockPowerHalController : public power::PowerHalController {
@@ -42,8 +42,9 @@
     MOCK_METHOD(void, init, (), (override));
     MOCK_METHOD(HalResult<void>, setBoost, (Boost, int32_t), (override));
     MOCK_METHOD(HalResult<void>, setMode, (Mode, bool), (override));
-    MOCK_METHOD(HalResult<sp<hardware::power::IPowerHintSession>>, createHintSession,
-                (int32_t, int32_t, const std::vector<int32_t>&, int64_t), (override));
+    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+                createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t),
+                (override));
     MOCK_METHOD(HalResult<int64_t>, getHintSessionPreferredRate, (), (override));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 8d57049..866af3b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -29,10 +29,19 @@
     EventThread();
     ~EventThread() override;
 
-    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection,
-                (ResyncCallback, EventRegistrationFlags), (const, override));
+    // TODO(b/302035909): workaround otherwise gtest complains about
+    //  error: no viable conversion from
+    //  'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
+    //  tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
+    sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
+        return createEventConnection(false, flags);
+    }
+    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
+                (const));
+
     MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
+    MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
     MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override));
     MOCK_METHOD(void, onFrameRateOverridesChanged,
                 (PhysicalDisplayId, std::vector<FrameRateOverride>), (override));
@@ -49,7 +58,6 @@
                 (const sp<android::EventThreadConnection>&), (const, override));
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
-    MOCK_METHOD(size_t, getEventThreadConnectionCount, (), (override));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
index 4cfdd58..bfcdd9b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
@@ -19,7 +19,7 @@
 #include <scheduler/FrameRateMode.h>
 
 // Use a C style macro to keep the line numbers printed in gtest
-#define EXPECT_FRAME_RATE_MODE(_modePtr, _fps, _mode)                                \
-    EXPECT_EQ((scheduler::FrameRateMode{(_fps), (_modePtr)}), (_mode))               \
-            << "Expected " << (_fps) << " (" << (_modePtr)->getFps() << ") but was " \
-            << (_mode).fps << " (" << (_mode).modePtr->getFps() << ")"
+#define EXPECT_FRAME_RATE_MODE(_modePtr, _fps, _mode)                                      \
+    EXPECT_EQ((scheduler::FrameRateMode{(_fps), (_modePtr)}), (_mode))                     \
+            << "Expected " << (_fps) << " (" << (_modePtr)->getVsyncRate() << ") but was " \
+            << (_mode).fps << " (" << (_mode).modePtr->getVsyncRate() << ")"
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
index 5dc48c3..3f2a917 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -31,7 +31,7 @@
 
     MOCK_METHOD0(onBootFinished, void());
     MOCK_METHOD1(addSurfaceFrame, void(std::shared_ptr<frametimeline::SurfaceFrame>));
-    MOCK_METHOD3(setSfWakeUp, void(int64_t, nsecs_t, Fps));
+    MOCK_METHOD4(setSfWakeUp, void(int64_t, nsecs_t, Fps, Fps));
     MOCK_METHOD3(setSfPresent,
                  void(nsecs_t, const std::shared_ptr<FenceTime>&,
                       const std::shared_ptr<FenceTime>&));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 50e07fc..4cc78fe 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -25,7 +25,7 @@
     MockLayer(SurfaceFlinger* flinger, std::string name)
           : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {
         EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
-                .WillOnce(testing::Return(scheduler::LayerInfo::FrameRateCompatibility::Default));
+                .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
     }
     explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
 
@@ -34,8 +34,7 @@
     MOCK_CONST_METHOD0(isVisible, bool());
     MOCK_METHOD1(createClone, sp<Layer>(uint32_t));
     MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
-    MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility,
-                       scheduler::LayerInfo::FrameRateCompatibility());
+    MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::FrameRateCompatibility());
     MOCK_CONST_METHOD0(getOwnerUid, uid_t());
     MOCK_CONST_METHOD0(getDataSpace, ui::Dataspace());
 };
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 306eb4d..22b2ccc 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -27,6 +27,7 @@
     MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
+    MOCK_METHOD(void, onChoreographerAttached, (), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
@@ -34,6 +35,7 @@
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
+    void onChoreographerAttached() override {}
 };
 
 } // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 86fbadc..c82e45b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -34,7 +34,6 @@
     MOCK_METHOD0(incrementTotalFrames, void());
     MOCK_METHOD0(incrementMissedFrames, void());
     MOCK_METHOD0(incrementRefreshRateSwitches, void());
-    MOCK_METHOD1(recordDisplayEventConnectionCount, void(int32_t));
     MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, const std::shared_ptr<FenceTime>&));
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index b033adb..7e382a3 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -1,7 +1,27 @@
 {
   "presubmit": [
     {
+      "name": "libvibratorservice_test",
+      "options": [
+        // TODO(b/293603710): Fix flakiness
+        {
+          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay"
+        },
+        // TODO(b/293623689): Fix flakiness
+        {
+          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleMultipleCallbacksRunsInDelayOrder"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
       "name": "libvibratorservice_test"
     }
+  ],
+  "imports": [
+    {
+      "path": "cts/tests/vibrator"
+    }
   ]
 }
diff --git a/services/vibratorservice/VibratorCallbackScheduler.cpp b/services/vibratorservice/VibratorCallbackScheduler.cpp
index f2870b0..7eda9ef 100644
--- a/services/vibratorservice/VibratorCallbackScheduler.cpp
+++ b/services/vibratorservice/VibratorCallbackScheduler.cpp
@@ -29,8 +29,11 @@
     return mExpiration <= std::chrono::steady_clock::now();
 }
 
-DelayedCallback::Timestamp DelayedCallback::getExpiration() const {
-    return mExpiration;
+std::chrono::milliseconds DelayedCallback::getWaitForExpirationDuration() const {
+    std::chrono::milliseconds delta = std::chrono::duration_cast<std::chrono::milliseconds>(
+            mExpiration - std::chrono::steady_clock::now());
+    // Return zero if this is already expired.
+    return delta > delta.zero() ? delta : delta.zero();
 }
 
 void DelayedCallback::run() const {
@@ -88,7 +91,9 @@
             mCondition.wait(mMutex);
         } else {
             // Wait until next callback expires, or a new one is scheduled.
-            mCondition.wait_until(mMutex, mQueue.top().getExpiration());
+            // Use the monotonic steady clock to wait for the measured delay interval via wait_for
+            // instead of using a wall clock via wait_until.
+            mCondition.wait_for(mMutex, mQueue.top().getWaitForExpirationDuration());
         }
     }
 }
diff --git a/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h b/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
index 2c194b5..c8ec15d 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
@@ -30,15 +30,13 @@
 // Wrapper for a callback to be executed after a delay.
 class DelayedCallback {
 public:
-    using Timestamp = std::chrono::time_point<std::chrono::steady_clock>;
-
     DelayedCallback(std::function<void()> callback, std::chrono::milliseconds delay)
           : mCallback(callback), mExpiration(std::chrono::steady_clock::now() + delay) {}
     ~DelayedCallback() = default;
 
     void run() const;
     bool isExpired() const;
-    Timestamp getExpiration() const;
+    std::chrono::milliseconds getWaitForExpirationDuration() const;
 
     // Compare by expiration time, where A < B when A expires first.
     bool operator<(const DelayedCallback& other) const;
@@ -46,7 +44,9 @@
 
 private:
     std::function<void()> mCallback;
-    Timestamp mExpiration;
+    // Use a steady monotonic clock to calculate the duration until expiration.
+    // This clock is not related to wall clock time and is most suitable for measuring intervals.
+    std::chrono::time_point<std::chrono::steady_clock> mExpiration;
 };
 
 // Schedules callbacks to be executed after a delay.
diff --git a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
index aaeb8f9..426cd42 100644
--- a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
+++ b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
@@ -38,6 +38,9 @@
 
 // -------------------------------------------------------------------------------------------------
 
+// Delay allowed for the scheduler to process callbacks during this test.
+static const auto TEST_TIMEOUT = 50ms;
+
 class VibratorCallbackSchedulerTest : public Test {
 public:
     void SetUp() override {
@@ -67,58 +70,57 @@
         return std::vector<int32_t>(mExpiredCallbacks);
     }
 
-    bool waitForCallbacks(uint32_t callbackCount, milliseconds timeout) {
-        time_point<steady_clock> expiration = steady_clock::now() + timeout;
-        while (steady_clock::now() < expiration) {
+    int32_t waitForCallbacks(int32_t callbackCount, milliseconds timeout) {
+        time_point<steady_clock> expirationTime = steady_clock::now() + timeout + TEST_TIMEOUT;
+        int32_t expiredCallbackCount = 0;
+        while (steady_clock::now() < expirationTime) {
             std::lock_guard<std::mutex> lock(mMutex);
-            if (callbackCount <= mExpiredCallbacks.size()) {
-                return true;
+            expiredCallbackCount = mExpiredCallbacks.size();
+            if (callbackCount <= expiredCallbackCount) {
+                return expiredCallbackCount;
             }
-            mCondition.wait_until(mMutex, expiration);
+            auto currentTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(
+                    expirationTime - steady_clock::now());
+            if (currentTimeout > currentTimeout.zero()) {
+                // Use the monotonic steady clock to wait for the requested timeout via wait_for
+                // instead of using a wall clock via wait_until.
+                mCondition.wait_for(mMutex, currentTimeout);
+            }
         }
-        return false;
+        return expiredCallbackCount;
     }
 };
 
 // -------------------------------------------------------------------------------------------------
 
 TEST_F(VibratorCallbackSchedulerTest, TestScheduleRunsOnlyAfterDelay) {
-    mScheduler->schedule(createCallback(1), 15ms);
+    time_point<steady_clock> startTime = steady_clock::now();
+    mScheduler->schedule(createCallback(1), 50ms);
 
-    // Not triggered before delay.
-    ASSERT_FALSE(waitForCallbacks(1, 10ms));
-    ASSERT_TRUE(getExpiredCallbacks().empty());
+    ASSERT_EQ(1, waitForCallbacks(1, 50ms));
+    time_point<steady_clock> callbackTime = steady_clock::now();
 
-    ASSERT_TRUE(waitForCallbacks(1, 10ms));
+    // Callback happened at least 50ms after the beginning of the test.
+    ASSERT_TRUE(startTime + 50ms <= callbackTime);
     ASSERT_THAT(getExpiredCallbacks(), ElementsAre(1));
 }
 
 TEST_F(VibratorCallbackSchedulerTest, TestScheduleMultipleCallbacksRunsInDelayOrder) {
-    mScheduler->schedule(createCallback(1), 10ms);
-    mScheduler->schedule(createCallback(2), 5ms);
-    mScheduler->schedule(createCallback(3), 1ms);
+    // Schedule first callbacks long enough that all 3 will be scheduled together and run in order.
+    mScheduler->schedule(createCallback(1), 50ms);
+    mScheduler->schedule(createCallback(2), 40ms);
+    mScheduler->schedule(createCallback(3), 10ms);
 
-    ASSERT_TRUE(waitForCallbacks(3, 15ms));
+    ASSERT_EQ(3, waitForCallbacks(3, 50ms));
     ASSERT_THAT(getExpiredCallbacks(), ElementsAre(3, 2, 1));
 }
 
-TEST_F(VibratorCallbackSchedulerTest, TestScheduleInParallelRunsInDelayOrder) {
-    std::vector<std::thread> threads;
-    for (int i = 0; i < 5; i++) {
-        threads.push_back(std::thread(
-                [=]() { mScheduler->schedule(createCallback(i), milliseconds(10 + 2 * i)); }));
-    }
-    std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
-
-    ASSERT_TRUE(waitForCallbacks(5, 25ms));
-    ASSERT_THAT(getExpiredCallbacks(), ElementsAre(0, 1, 2, 3, 4));
-}
-
 TEST_F(VibratorCallbackSchedulerTest, TestDestructorDropsPendingCallbacksAndKillsThread) {
-    mScheduler->schedule(createCallback(1), 5ms);
+    // Schedule callback long enough that scheduler will be destroyed while it's still scheduled.
+    mScheduler->schedule(createCallback(1), 50ms);
     mScheduler.reset(nullptr);
 
-    // Should time out waiting for callback to run.
-    ASSERT_FALSE(waitForCallbacks(1, 10ms));
+    // Should timeout waiting for callback to run.
+    ASSERT_EQ(0, waitForCallbacks(1, 50ms));
     ASSERT_TRUE(getExpiredCallbacks().empty());
 }
diff --git a/vulkan/include/vulkan/vk_android_native_buffer.h b/vulkan/include/vulkan/vk_android_native_buffer.h
index e78f470..7c8e695 100644
--- a/vulkan/include/vulkan/vk_android_native_buffer.h
+++ b/vulkan/include/vulkan/vk_android_native_buffer.h
@@ -60,7 +60,12 @@
  *
  * This version of the extension cleans up a bug introduced in version 9
  */
-#define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 10
+/*
+ * NOTE ON VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
+ *
+ * This version of the extension deprecates the last of grallocusage
+ */
+#define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
 #define VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME "VK_ANDROID_native_buffer"
 
 #define VK_ANDROID_NATIVE_BUFFER_ENUM(type, id) \
@@ -151,6 +156,8 @@
  * pNext: NULL or a pointer to a structure extending this structure
  * format: value specifying the format the image will be created with
  * imageUsage: bitmask of VkImageUsageFlagBits describing intended usage
+ *
+ * DEPRECATED in SPEC_VERSION 10
  */
 typedef struct {
     VkStructureType                   sType;
@@ -167,6 +174,8 @@
  * format: value specifying the format the image will be created with
  * imageUsage: bitmask of VkImageUsageFlagBits describing intended usage
  * swapchainImageUsage: is a bitmask of VkSwapchainImageUsageFlagsANDROID
+ *
+ * DEPRECATED in SPEC_VERSION 11
  */
 typedef struct {
     VkStructureType                   sType;
@@ -198,7 +207,7 @@
     const VkGrallocUsageInfoANDROID*  grallocUsageInfo,
     uint64_t*                         grallocUsage);
 
-/* ADDED in SPEC_VERSION 10 */
+/* DEPRECATED in SPEC_VERSION 11 */
 typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsage4ANDROID)(
     VkDevice                          device,
     const VkGrallocUsageInfo2ANDROID* grallocUsageInfo,
@@ -245,7 +254,7 @@
     uint64_t*                         grallocUsage
 );
 
-/* ADDED in SPEC_VERSION 10 */
+/* DEPRECATED in SPEC_VERSION 11 */
 VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainGrallocUsage4ANDROID(
     VkDevice                          device,
     const VkGrallocUsageInfo2ANDROID* grallocUsageInfo,
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index bdba27e..5d7a4aa 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -1456,6 +1456,7 @@
     }
 
     data->driver_device = dev;
+    data->driver_physical_device = physicalDevice;
 
     *pDevice = dev;
 
diff --git a/vulkan/libvulkan/driver.h b/vulkan/libvulkan/driver.h
index 4d2bbd6..4b855e5 100644
--- a/vulkan/libvulkan/driver.h
+++ b/vulkan/libvulkan/driver.h
@@ -98,6 +98,7 @@
 
     VkDevice driver_device;
     DeviceDriverTable driver;
+    VkPhysicalDevice driver_physical_device;
 };
 
 bool OpenHAL();
diff --git a/vulkan/libvulkan/layers_extensions.cpp b/vulkan/libvulkan/layers_extensions.cpp
index d059f8f..c073579 100644
--- a/vulkan/libvulkan/layers_extensions.cpp
+++ b/vulkan/libvulkan/layers_extensions.cpp
@@ -74,6 +74,7 @@
           filename_(filename),
           dlhandle_(nullptr),
           native_bridge_(false),
+          opened_with_native_loader_(false),
           refcount_(0) {}
 
     LayerLibrary(LayerLibrary&& other) noexcept
@@ -81,6 +82,7 @@
           filename_(std::move(other.filename_)),
           dlhandle_(other.dlhandle_),
           native_bridge_(other.native_bridge_),
+          opened_with_native_loader_(other.opened_with_native_loader_),
           refcount_(other.refcount_) {
         other.dlhandle_ = nullptr;
         other.refcount_ = 0;
@@ -120,6 +122,7 @@
     std::mutex mutex_;
     void* dlhandle_;
     bool native_bridge_;
+    bool opened_with_native_loader_;
     size_t refcount_;
 };
 
@@ -136,7 +139,7 @@
         if (app_namespace &&
             !android::base::StartsWith(path_, kSystemLayerLibraryDir)) {
             char* error_msg = nullptr;
-            dlhandle_ = OpenNativeLibraryInNamespace(
+            dlhandle_ = android::OpenNativeLibraryInNamespace(
                 app_namespace, path_.c_str(), &native_bridge_, &error_msg);
             if (!dlhandle_) {
                 ALOGE("failed to load layer library '%s': %s", path_.c_str(), error_msg);
@@ -144,14 +147,16 @@
                 refcount_ = 0;
                 return false;
             }
+            opened_with_native_loader_ = true;
         } else {
-          dlhandle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL);
+            dlhandle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL);
             if (!dlhandle_) {
                 ALOGE("failed to load layer library '%s': %s", path_.c_str(),
                       dlerror());
                 refcount_ = 0;
                 return false;
             }
+            opened_with_native_loader_ = false;
         }
     }
     return true;
@@ -161,14 +166,25 @@
     std::lock_guard<std::mutex> lock(mutex_);
     if (--refcount_ == 0) {
         ALOGV("closing layer library '%s'", path_.c_str());
-        char* error_msg = nullptr;
-        if (!android::CloseNativeLibrary(dlhandle_, native_bridge_, &error_msg)) {
-            ALOGE("failed to unload library '%s': %s", path_.c_str(), error_msg);
-            android::NativeLoaderFreeErrorMessage(error_msg);
-            refcount_++;
+        // we close the .so same way as we opened. It's importain, because
+        // android::CloseNativeLibrary lives in libnativeloader.so, which is
+        // not accessible for early loaded services like SurfaceFlinger
+        if (opened_with_native_loader_) {
+            char* error_msg = nullptr;
+            if (!android::CloseNativeLibrary(dlhandle_, native_bridge_, &error_msg)) {
+                ALOGE("failed to unload library '%s': %s", path_.c_str(), error_msg);
+                android::NativeLoaderFreeErrorMessage(error_msg);
+                refcount_++;
+                return;
+            }
         } else {
-           dlhandle_ = nullptr;
+            if (dlclose(dlhandle_) != 0) {
+                ALOGE("failed to unload library '%s': %s", path_.c_str(), dlerror());
+                refcount_++;
+                return;
+            }
         }
+        dlhandle_ = nullptr;
     }
 }
 
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index bffbe9d..0c48611 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -16,8 +16,10 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
 #include <aidl/android/hardware/graphics/common/PixelFormat.h>
 #include <android/hardware/graphics/common/1.0/types.h>
+#include <android/hardware_buffer.h>
 #include <grallocusage/GrallocUsageConversion.h>
 #include <graphicsenv/GraphicsEnv.h>
 #include <hardware/gralloc.h>
@@ -37,6 +39,7 @@
 #include "driver.h"
 
 using PixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+using DataSpace = aidl::android::hardware::graphics::common::Dataspace;
 using android::hardware::graphics::common::V1_0::BufferUsage;
 
 namespace vulkan {
@@ -532,61 +535,51 @@
     return native_format;
 }
 
-android_dataspace GetNativeDataspace(VkColorSpaceKHR colorspace,
-                                     PixelFormat pixelFormat) {
+DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace,
+                             PixelFormat pixelFormat) {
     switch (colorspace) {
         case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
-            return HAL_DATASPACE_V0_SRGB;
+            return DataSpace::SRGB;
         case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT:
-            return HAL_DATASPACE_DISPLAY_P3;
+            return DataSpace::DISPLAY_P3;
         case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
-            return HAL_DATASPACE_V0_SCRGB_LINEAR;
+            return DataSpace::SCRGB_LINEAR;
         case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT:
-            return HAL_DATASPACE_V0_SCRGB;
+            return DataSpace::SCRGB;
         case VK_COLOR_SPACE_DCI_P3_LINEAR_EXT:
-            return HAL_DATASPACE_DCI_P3_LINEAR;
+            return DataSpace::DCI_P3_LINEAR;
         case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT:
-            return HAL_DATASPACE_DCI_P3;
+            return DataSpace::DCI_P3;
         case VK_COLOR_SPACE_BT709_LINEAR_EXT:
-            return HAL_DATASPACE_V0_SRGB_LINEAR;
+            return DataSpace::SRGB_LINEAR;
         case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
-            return HAL_DATASPACE_V0_SRGB;
+            return DataSpace::SRGB;
         case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
             if (pixelFormat == PixelFormat::RGBA_FP16) {
-                return static_cast<android_dataspace>(
-                    HAL_DATASPACE_STANDARD_BT2020 |
-                    HAL_DATASPACE_TRANSFER_LINEAR |
-                    HAL_DATASPACE_RANGE_EXTENDED);
+                return DataSpace::BT2020_LINEAR_EXTENDED;
             } else {
-                return HAL_DATASPACE_BT2020_LINEAR;
+                return DataSpace::BT2020_LINEAR;
             }
         case VK_COLOR_SPACE_HDR10_ST2084_EXT:
-            return static_cast<android_dataspace>(
-                HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_ST2084 |
-                HAL_DATASPACE_RANGE_FULL);
+            return DataSpace::BT2020_PQ;
         case VK_COLOR_SPACE_DOLBYVISION_EXT:
-            return static_cast<android_dataspace>(
-                HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_ST2084 |
-                HAL_DATASPACE_RANGE_FULL);
+            return DataSpace::BT2020_PQ;
         case VK_COLOR_SPACE_HDR10_HLG_EXT:
-            return static_cast<android_dataspace>(HAL_DATASPACE_BT2020_HLG);
+            return DataSpace::BT2020_HLG;
         case VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT:
-            return static_cast<android_dataspace>(
-                HAL_DATASPACE_STANDARD_ADOBE_RGB |
-                HAL_DATASPACE_TRANSFER_LINEAR | HAL_DATASPACE_RANGE_FULL);
+            return DataSpace::ADOBE_RGB_LINEAR;
         case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT:
-            return HAL_DATASPACE_ADOBE_RGB;
-
+            return DataSpace::ADOBE_RGB;
         // Pass through is intended to allow app to provide data that is passed
         // to the display system without modification.
         case VK_COLOR_SPACE_PASS_THROUGH_EXT:
-            return HAL_DATASPACE_ARBITRARY;
+            return DataSpace::ARBITRARY;
 
         default:
             // This indicates that we don't know about the
             // dataspace specified and we should indicate that
             // it's unsupported
-            return HAL_DATASPACE_UNKNOWN;
+            return DataSpace::UNKNOWN;
     }
 }
 
@@ -944,16 +937,40 @@
             return VK_ERROR_SURFACE_LOST_KHR;
         }
 
-        if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) {
-            capabilities->minImageCount = 1;
-            capabilities->maxImageCount = 1;
-        } else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
-            capabilities->minImageCount =
-                std::min(max_buffer_count, min_undequeued_buffers + 2);
-            capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+        // Additional buffer count over min_undequeued_buffers in vulkan came from 2 total
+        // being technically enough for fifo (although a poor experience) vs 3 being the
+        // absolute minimum for mailbox to be useful. So min_undequeued_buffers + 2 is sensible
+        static constexpr int default_additional_buffers = 2;
+
+        if(pPresentMode != nullptr) {
+            switch (pPresentMode->presentMode) {
+                case VK_PRESENT_MODE_IMMEDIATE_KHR:
+                    ALOGE("Swapchain present mode VK_PRESENT_MODE_IMMEDIATE_KHR is not supported");
+                    break;
+                case VK_PRESENT_MODE_MAILBOX_KHR:
+                case VK_PRESENT_MODE_FIFO_KHR:
+                    capabilities->minImageCount = std::min(max_buffer_count,
+                            min_undequeued_buffers + default_additional_buffers);
+                    capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+                    break;
+                case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
+                    ALOGE("Swapchain present mode VK_PRESENT_MODE_FIFO_RELEAXED_KHR "
+                          "is not supported");
+                    break;
+                case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
+                case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
+                    capabilities->minImageCount = 1;
+                    capabilities->maxImageCount = 1;
+                    break;
+
+                default:
+                    ALOGE("Unrecognized swapchain present mode %u is not supported",
+                            pPresentMode->presentMode);
+                    break;
+            }
         } else {
-            capabilities->minImageCount =
-                std::min(max_buffer_count, min_undequeued_buffers + 1);
+            capabilities->minImageCount = std::min(max_buffer_count,
+                    min_undequeued_buffers + default_additional_buffers);
             capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
         }
     }
@@ -1343,6 +1360,187 @@
     allocator->pfnFree(allocator->pUserData, swapchain);
 }
 
+static VkResult getProducerUsage(const VkDevice& device,
+                                 const VkSwapchainCreateInfoKHR* create_info,
+                                 const VkSwapchainImageUsageFlagsANDROID swapchain_image_usage,
+                                 bool create_protected_swapchain,
+                                 uint64_t* producer_usage) {
+    // Get the physical device to query the appropriate producer usage
+    const VkPhysicalDevice& pdev = GetData(device).driver_physical_device;
+    const InstanceData& instance_data = GetData(pdev);
+    const InstanceDriverTable& instance_dispatch = instance_data.driver;
+    if (!instance_dispatch.GetPhysicalDeviceImageFormatProperties2 &&
+            !instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR) {
+        uint64_t native_usage = 0;
+        void* usage_info_pNext = nullptr;
+        VkResult result;
+        VkImageCompressionControlEXT image_compression = {};
+        const auto& dispatch = GetData(device).driver;
+        if (dispatch.GetSwapchainGrallocUsage4ANDROID) {
+            ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID");
+            VkGrallocUsageInfo2ANDROID gralloc_usage_info = {};
+            gralloc_usage_info.sType =
+                VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID;
+            gralloc_usage_info.format = create_info->imageFormat;
+            gralloc_usage_info.imageUsage = create_info->imageUsage;
+            gralloc_usage_info.swapchainImageUsage = swapchain_image_usage;
+
+            // Look through the pNext chain for an image compression control struct
+            // if one is found AND the appropriate extensions are enabled,
+            // append it to be the gralloc usage pNext chain
+            const VkSwapchainCreateInfoKHR* create_infos = create_info;
+            while (create_infos->pNext) {
+                create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
+                    create_infos->pNext);
+                switch (create_infos->sType) {
+                    case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                        const VkImageCompressionControlEXT* compression_infos =
+                            reinterpret_cast<const VkImageCompressionControlEXT*>(
+                                create_infos);
+                        image_compression = *compression_infos;
+                        image_compression.pNext = nullptr;
+                        usage_info_pNext = &image_compression;
+                    } break;
+
+                    default:
+                        // Ignore all other info structs
+                        break;
+                }
+            }
+            gralloc_usage_info.pNext = usage_info_pNext;
+
+            result = dispatch.GetSwapchainGrallocUsage4ANDROID(
+                device, &gralloc_usage_info, &native_usage);
+            ATRACE_END();
+            if (result != VK_SUCCESS) {
+                ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result);
+                return VK_ERROR_SURFACE_LOST_KHR;
+            }
+        } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
+            ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
+            VkGrallocUsageInfoANDROID gralloc_usage_info = {};
+            gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
+            gralloc_usage_info.format = create_info->imageFormat;
+            gralloc_usage_info.imageUsage = create_info->imageUsage;
+
+            // Look through the pNext chain for an image compression control struct
+            // if one is found AND the appropriate extensions are enabled,
+            // append it to be the gralloc usage pNext chain
+            const VkSwapchainCreateInfoKHR* create_infos = create_info;
+            while (create_infos->pNext) {
+                create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
+                    create_infos->pNext);
+                switch (create_infos->sType) {
+                    case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                        const VkImageCompressionControlEXT* compression_infos =
+                            reinterpret_cast<const VkImageCompressionControlEXT*>(
+                                create_infos);
+                        image_compression = *compression_infos;
+                        image_compression.pNext = nullptr;
+                        usage_info_pNext = &image_compression;
+                    } break;
+
+                    default:
+                        // Ignore all other info structs
+                        break;
+                }
+            }
+            gralloc_usage_info.pNext = usage_info_pNext;
+
+            result = dispatch.GetSwapchainGrallocUsage3ANDROID(
+                device, &gralloc_usage_info, &native_usage);
+            ATRACE_END();
+            if (result != VK_SUCCESS) {
+                ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
+                return VK_ERROR_SURFACE_LOST_KHR;
+            }
+        } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
+            uint64_t consumer_usage, producer_usage;
+            ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
+            result = dispatch.GetSwapchainGrallocUsage2ANDROID(
+                device, create_info->imageFormat, create_info->imageUsage,
+                swapchain_image_usage, &consumer_usage, &producer_usage);
+            ATRACE_END();
+            if (result != VK_SUCCESS) {
+                ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result);
+                return VK_ERROR_SURFACE_LOST_KHR;
+            }
+            native_usage =
+                convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
+        } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
+            ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
+            int32_t legacy_usage = 0;
+            result = dispatch.GetSwapchainGrallocUsageANDROID(
+                device, create_info->imageFormat, create_info->imageUsage,
+                &legacy_usage);
+            ATRACE_END();
+            if (result != VK_SUCCESS) {
+                ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result);
+                return VK_ERROR_SURFACE_LOST_KHR;
+            }
+            native_usage = static_cast<uint64_t>(legacy_usage);
+        }
+        *producer_usage = native_usage;
+
+        return VK_SUCCESS;
+    }
+
+    // call GetPhysicalDeviceImageFormatProperties2KHR
+    VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = {
+        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
+        .pNext = nullptr,
+        .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
+    };
+
+    // AHB does not have an sRGB format so we can't pass it to GPDIFP
+    // We need to convert the format to unorm if it is srgb
+    VkFormat format = create_info->imageFormat;
+    if (format == VK_FORMAT_R8G8B8A8_SRGB) {
+        format = VK_FORMAT_R8G8B8A8_UNORM;
+    }
+
+    VkPhysicalDeviceImageFormatInfo2 image_format_info = {
+        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+        .pNext = &external_image_format_info,
+        .format = format,
+        .type = VK_IMAGE_TYPE_2D,
+        .tiling = VK_IMAGE_TILING_OPTIMAL,
+        .usage = create_info->imageUsage,
+        .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
+    };
+
+    VkAndroidHardwareBufferUsageANDROID ahb_usage;
+    ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
+    ahb_usage.pNext = nullptr;
+
+    VkImageFormatProperties2 image_format_properties;
+    image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
+    image_format_properties.pNext = &ahb_usage;
+
+    if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
+        VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
+            pdev, &image_format_info, &image_format_properties);
+        if (result != VK_SUCCESS) {
+            ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+    }
+    else {
+        VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
+            pdev, &image_format_info,
+            &image_format_properties);
+        if (result != VK_SUCCESS) {
+            ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
+                result);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+    }
+
+    *producer_usage = ahb_usage.androidHardwareBufferUsage;
+
+    return VK_SUCCESS;
+}
+
 VKAPI_ATTR
 VkResult CreateSwapchainKHR(VkDevice device,
                             const VkSwapchainCreateInfoKHR* create_info,
@@ -1369,9 +1567,9 @@
 
     PixelFormat native_pixel_format =
         GetNativePixelFormat(create_info->imageFormat);
-    android_dataspace native_dataspace =
+    DataSpace native_dataspace =
         GetNativeDataspace(create_info->imageColorSpace, native_pixel_format);
-    if (native_dataspace == HAL_DATASPACE_UNKNOWN) {
+    if (native_dataspace == DataSpace::UNKNOWN) {
         ALOGE(
             "CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) "
             "failed: Unsupported color space",
@@ -1477,8 +1675,9 @@
     }
 
     /* Respect consumer default dataspace upon HAL_DATASPACE_ARBITRARY. */
-    if (native_dataspace != HAL_DATASPACE_ARBITRARY) {
-        err = native_window_set_buffers_data_space(window, native_dataspace);
+    if (native_dataspace != DataSpace::ARBITRARY) {
+        err = native_window_set_buffers_data_space(
+            window, static_cast<android_dataspace_t>(native_dataspace));
         if (err != android::OK) {
             ALOGE("native_window_set_buffers_data_space(%d) failed: %s (%d)",
                   native_dataspace, strerror(-err), err);
@@ -1547,22 +1746,26 @@
               query_value);
         return VK_ERROR_SURFACE_LOST_KHR;
     }
-    uint32_t min_undequeued_buffers = static_cast<uint32_t>(query_value);
-    const auto mailbox_num_images = std::max(3u, create_info->minImageCount);
-    const auto requested_images =
-        swap_interval ? create_info->minImageCount : mailbox_num_images;
-    uint32_t num_images = requested_images - 1 + min_undequeued_buffers;
+    const uint32_t min_undequeued_buffers = static_cast<uint32_t>(query_value);
 
     // Lower layer insists that we have at least min_undequeued_buffers + 1
     // buffers.  This is wasteful and we'd like to relax it in the shared case,
     // but not all the pieces are in place for that to work yet.  Note we only
     // lie to the lower layer--we don't want to give the app back a swapchain
     // with extra images (which they can't actually use!).
-    uint32_t min_buffer_count = min_undequeued_buffers + 1;
-    err = native_window_set_buffer_count(
-        window, std::max(min_buffer_count, num_images));
+    const uint32_t min_buffer_count = min_undequeued_buffers + 1;
+
+    uint32_t num_images;
+    if (create_info->presentMode  == VK_PRESENT_MODE_MAILBOX_KHR) {
+        num_images = std::max(3u, create_info->minImageCount);
+    } else {
+        num_images = create_info->minImageCount;
+    }
+
+    const uint32_t buffer_count = std::max(min_buffer_count, num_images);
+    err = native_window_set_buffer_count(window, buffer_count);
     if (err != android::OK) {
-        ALOGE("native_window_set_buffer_count(%d) failed: %s (%d)", num_images,
+        ALOGE("native_window_set_buffer_count(%d) failed: %s (%d)", buffer_count,
               strerror(-err), err);
         return VK_ERROR_SURFACE_LOST_KHR;
     }
@@ -1573,120 +1776,48 @@
         num_images = 1;
     }
 
+    // Look through the create_info pNext chain passed to createSwapchainKHR
+    // for an image compression control struct.
+    // if one is found AND the appropriate extensions are enabled, create a
+    // VkImageCompressionControlEXT structure to pass on to VkImageCreateInfo
+    // TODO check for imageCompressionControlSwapchain feature is enabled
     void* usage_info_pNext = nullptr;
     VkImageCompressionControlEXT image_compression = {};
-    uint64_t native_usage = 0;
-    if (dispatch.GetSwapchainGrallocUsage4ANDROID) {
-        ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID");
-        VkGrallocUsageInfo2ANDROID gralloc_usage_info = {};
-        gralloc_usage_info.sType =
-            VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID;
-        gralloc_usage_info.format = create_info->imageFormat;
-        gralloc_usage_info.imageUsage = create_info->imageUsage;
-        gralloc_usage_info.swapchainImageUsage = swapchain_image_usage;
+    const VkSwapchainCreateInfoKHR* create_infos = create_info;
+    while (create_infos->pNext) {
+        create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext);
+        switch (create_infos->sType) {
+            case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                const VkImageCompressionControlEXT* compression_infos =
+                    reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos);
+                image_compression = *compression_infos;
+                image_compression.pNext = nullptr;
+                usage_info_pNext = &image_compression;
+            } break;
 
-        // Look through the pNext chain for an image compression control struct
-        // if one is found AND the appropriate extensions are enabled,
-        // append it to be the gralloc usage pNext chain
-        const VkSwapchainCreateInfoKHR* create_infos = create_info;
-        while (create_infos->pNext) {
-            create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
-                create_infos->pNext);
-            switch (create_infos->sType) {
-                case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
-                    const VkImageCompressionControlEXT* compression_infos =
-                        reinterpret_cast<const VkImageCompressionControlEXT*>(
-                            create_infos);
-                    image_compression = *compression_infos;
-                    image_compression.pNext = nullptr;
-                    usage_info_pNext = &image_compression;
-                } break;
-
-                default:
-                    // Ignore all other info structs
-                    break;
-            }
+            default:
+                // Ignore all other info structs
+                break;
         }
-        gralloc_usage_info.pNext = usage_info_pNext;
-
-        result = dispatch.GetSwapchainGrallocUsage4ANDROID(
-            device, &gralloc_usage_info, &native_usage);
-        ATRACE_END();
-        if (result != VK_SUCCESS) {
-            ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-    } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
-        ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
-        VkGrallocUsageInfoANDROID gralloc_usage_info = {};
-        gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
-        gralloc_usage_info.format = create_info->imageFormat;
-        gralloc_usage_info.imageUsage = create_info->imageUsage;
-
-        // Look through the pNext chain for an image compression control struct
-        // if one is found AND the appropriate extensions are enabled,
-        // append it to be the gralloc usage pNext chain
-        const VkSwapchainCreateInfoKHR* create_infos = create_info;
-        while (create_infos->pNext) {
-            create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
-                create_infos->pNext);
-            switch (create_infos->sType) {
-                case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
-                    const VkImageCompressionControlEXT* compression_infos =
-                        reinterpret_cast<const VkImageCompressionControlEXT*>(
-                            create_infos);
-                    image_compression = *compression_infos;
-                    image_compression.pNext = nullptr;
-                    usage_info_pNext = &image_compression;
-                } break;
-
-                default:
-                    // Ignore all other info structs
-                    break;
-            }
-        }
-        gralloc_usage_info.pNext = usage_info_pNext;
-
-        result = dispatch.GetSwapchainGrallocUsage3ANDROID(
-            device, &gralloc_usage_info, &native_usage);
-        ATRACE_END();
-        if (result != VK_SUCCESS) {
-            ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-    } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
-        uint64_t consumer_usage, producer_usage;
-        ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
-        result = dispatch.GetSwapchainGrallocUsage2ANDROID(
-            device, create_info->imageFormat, create_info->imageUsage,
-            swapchain_image_usage, &consumer_usage, &producer_usage);
-        ATRACE_END();
-        if (result != VK_SUCCESS) {
-            ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        native_usage =
-            convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
-    } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
-        ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
-        int32_t legacy_usage = 0;
-        result = dispatch.GetSwapchainGrallocUsageANDROID(
-            device, create_info->imageFormat, create_info->imageUsage,
-            &legacy_usage);
-        ATRACE_END();
-        if (result != VK_SUCCESS) {
-            ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        native_usage = static_cast<uint64_t>(legacy_usage);
     }
-    native_usage |= surface.consumer_usage;
 
-    bool createProtectedSwapchain = false;
+    // Get the appropriate native_usage for the images
+    // Get the consumer usage
+    uint64_t native_usage = surface.consumer_usage;
+    // Determine if the swapchain is protected
+    bool create_protected_swapchain = false;
     if (create_info->flags & VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR) {
-        createProtectedSwapchain = true;
+        create_protected_swapchain = true;
         native_usage |= BufferUsage::PROTECTED;
     }
+    // Get the producer usage
+    uint64_t producer_usage;
+    result = getProducerUsage(device, create_info, swapchain_image_usage, create_protected_swapchain, &producer_usage);
+    if (result != VK_SUCCESS) {
+        return result;
+    }
+    native_usage |= producer_usage;
+
     err = native_window_set_usage(window, native_usage);
     if (err != android::OK) {
         ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), err);
@@ -1714,8 +1845,10 @@
     void* mem = allocator->pfnAllocation(allocator->pUserData,
                                          sizeof(Swapchain), alignof(Swapchain),
                                          VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
+
     if (!mem)
         return VK_ERROR_OUT_OF_HOST_MEMORY;
+
     Swapchain* swapchain = new (mem)
         Swapchain(surface, num_images, create_info->presentMode,
                   TranslateVulkanToNativeTransform(create_info->preTransform),
@@ -1739,7 +1872,7 @@
     VkImageCreateInfo image_create = {
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
         .pNext = nullptr,
-        .flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
+        .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
         .imageType = VK_IMAGE_TYPE_2D,
         .format = create_info->imageFormat,
         .extent = {
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
index b544245..de4271d 100644
--- a/vulkan/vkjson/Android.bp
+++ b/vulkan/vkjson/Android.bp
@@ -7,8 +7,19 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_defaults {
+    name: "libvkjson_deps",
+    shared_libs: [
+        "libjsoncpp",
+        "libvulkan",
+    ],
+}
+
+cc_library_static {
     name: "libvkjson",
+    defaults: [
+        "libvkjson_deps",
+    ],
     srcs: [
         "vkjson.cc",
         "vkjson_instance.cc",
@@ -24,10 +35,6 @@
     export_include_dirs: [
         ".",
     ],
-    shared_libs: [
-        "libjsoncpp",
-        "libvulkan",
-    ],
     export_shared_lib_headers: [
         "libvulkan",
     ],