Merge "Use dimming ratio when detecting hdr type for local TMO" into main
diff --git a/cmds/cmd/Android.bp b/cmds/cmd/Android.bp
index 27ef788..cf27e19 100644
--- a/cmds/cmd/Android.bp
+++ b/cmds/cmd/Android.bp
@@ -27,14 +27,10 @@
         "libselinux",
         "libbinder",
     ],
-    whole_static_libs: [
-        "libc++fs",
-    ],
 
     cflags: [
         "-Wall",
         "-Werror",
-        "-DXP_UNIX",
     ],
 }
 
@@ -58,6 +54,5 @@
     cflags: [
         "-Wall",
         "-Werror",
-        "-DXP_UNIX",
     ],
 }
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index a1c10f5..b22cc2a 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -159,7 +159,6 @@
         "tests/dumpstate_test.cpp",
     ],
     static_libs: [
-        "libc++fs",
         "libgmock",
     ],
     test_config: "dumpstate_test.xml",
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 1b61e3a..6576ffd 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -2195,6 +2195,74 @@
     ds.AddDir(LOGPERSIST_DATA_DIR, false);
 }
 
+static std::string GetTimestamp(const timespec& ts) {
+    tm tm;
+    localtime_r(&ts.tv_sec, &tm);
+
+    // Reserve enough space for the entire time string, includes the space
+    // for the '\0' to make the calculations below easier by using size for
+    // the total string size.
+    std::string str(sizeof("1970-01-01 00:00:00.123456789+0830"), '\0');
+    size_t n = strftime(str.data(), str.size(), "%F %H:%M", &tm);
+    if (n == 0) {
+        return "TIMESTAMP FAILURE";
+    }
+    int num_chars = snprintf(&str[n], str.size() - n, ":%02d.%09ld", tm.tm_sec, ts.tv_nsec);
+    if (num_chars > str.size() - n) {
+        return "TIMESTAMP FAILURE";
+    }
+    n += static_cast<size_t>(num_chars);
+    if (strftime(&str[n], str.size() - n, "%z", &tm) == 0) {
+        return "TIMESTAMP FAILURE";
+    }
+    return str;
+}
+
+static std::string GetCmdline(pid_t pid) {
+    std::string cmdline;
+    if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid),
+                                         &cmdline)) {
+        return "UNKNOWN";
+    }
+    // There are '\0' terminators between arguments, convert them to spaces.
+    // But start by skipping all trailing '\0' values.
+    size_t cur = cmdline.size() - 1;
+    while (cur != 0 && cmdline[cur] == '\0') {
+        cur--;
+    }
+    if (cur == 0) {
+        return "UNKNOWN";
+    }
+    while ((cur = cmdline.rfind('\0', cur)) != std::string::npos) {
+        cmdline[cur] = ' ';
+    }
+    return cmdline;
+}
+
+static void DumpPidHeader(int fd, pid_t pid, const timespec& ts) {
+    // For consistency, the header to this message matches the one
+    // dumped by debuggerd.
+    dprintf(fd, "\n----- pid %d at %s -----\n", pid, GetTimestamp(ts).c_str());
+    dprintf(fd, "Cmd line: %s\n", GetCmdline(pid).c_str());
+}
+
+static void DumpPidFooter(int fd, pid_t pid) {
+    // For consistency, the footer to this message matches the one
+    // dumped by debuggerd.
+    dprintf(fd, "----- end %d -----\n", pid);
+}
+
+static bool DumpBacktrace(int fd, pid_t pid, bool is_java_process) {
+    int ret = dump_backtrace_to_file_timeout(
+        pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace, 3, fd);
+    if (ret == -1 && is_java_process) {
+        // Tried to unwind as a java process, try a native unwind.
+        dprintf(fd, "Java unwind failed for pid %d, trying a native unwind.\n", pid);
+        ret = dump_backtrace_to_file_timeout(pid, kDebuggerdNativeBacktrace, 3, fd);
+    }
+    return ret != -1;
+}
+
 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;
@@ -2243,16 +2311,6 @@
             continue;
         }
 
-        // Skip cached processes.
-        if (IsCached(pid)) {
-            // For consistency, the header and footer to this message match those
-            // dumped by debuggerd in the success case.
-            dprintf(fd, "\n---- pid %d at [unknown] ----\n", pid);
-            dprintf(fd, "Dump skipped for cached process.\n");
-            dprintf(fd, "---- end %d ----", pid);
-            continue;
-        }
-
         const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
         std::string exe;
         if (!android::base::Readlink(link_name, &exe)) {
@@ -2281,16 +2339,31 @@
             break;
         }
 
-        const uint64_t start = Nanotime();
-        const int ret = dump_backtrace_to_file_timeout(
-            pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace, 3, fd);
+        timespec start_timespec;
+        clock_gettime(CLOCK_REALTIME, &start_timespec);
+        if (IsCached(pid)) {
+            DumpPidHeader(fd, pid, start_timespec);
+            dprintf(fd, "Process is cached, skipping backtrace due to high chance of timeout.\n");
+            DumpPidFooter(fd, pid);
+            continue;
+        }
 
-        if (ret == -1) {
-            // For consistency, the header and footer to this message match those
-            // dumped by debuggerd in the success case.
-            dprintf(fd, "\n---- pid %d at [unknown] ----\n", pid);
-            dprintf(fd, "Dump failed, likely due to a timeout.\n");
-            dprintf(fd, "---- end %d ----", pid);
+        const uint64_t start = Nanotime();
+        if (!DumpBacktrace(fd, pid, is_java_process)) {
+            if (IsCached(pid)) {
+                DumpPidHeader(fd, pid, start_timespec);
+                dprintf(fd, "Backtrace failed, but process has become cached.\n");
+                DumpPidFooter(fd, pid);
+                continue;
+            }
+
+            DumpPidHeader(fd, pid, start_timespec);
+            dprintf(fd, "Backtrace gathering failed, likely due to a timeout.\n");
+            DumpPidFooter(fd, pid);
+
+            dprintf(fd, "\n[dump %s stack %d: %.3fs elapsed]\n",
+                    is_java_process ? "dalvik" : "native", pid,
+                    (float)(Nanotime() - start) / NANOS_PER_SEC);
             timeout_failures++;
             continue;
         }
diff --git a/cmds/installd/file_parsing.h b/cmds/installd/file_parsing.h
index 88801ca..0ec5abb 100644
--- a/cmds/installd/file_parsing.h
+++ b/cmds/installd/file_parsing.h
@@ -51,7 +51,7 @@
 }
 
 template<typename Func>
-bool ParseFile(std::string_view str_file, Func parse) {
+bool ParseFile(const std::string& str_file, Func parse) {
   std::ifstream ifs(str_file);
   if (!ifs.is_open()) {
     return false;
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index ae7d8e0..9384926 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -50,11 +50,36 @@
   exit 1
 fi
 
-if pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then
-  # Handled by Pre-reboot Dexopt.
-  exit 0
+# A source that infinitely emits arbitrary lines.
+# When connected to STDIN of another process, this source keeps STDIN open until
+# the consumer process closes STDIN or this script dies.
+function infinite_source {
+  while echo .; do
+    sleep 1
+  done
+}
+
+PR_DEXOPT_JOB_VERSION="$(pm art pr-dexopt-job --version)"
+if (( $? == 0 )) && (( $PR_DEXOPT_JOB_VERSION >= 2 )); then
+  # Delegate to Pre-reboot Dexopt, a feature of ART Service.
+  # ART Service decides what to do with this request:
+  # - If Pre-reboot Dexopt is disabled or unsupported, the command returns
+  #   non-zero. This is always the case if the current system is Android 14 or
+  #   earlier.
+  # - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks
+  #   until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds or
+  #   not. This is the default behavior if the current system is Android 15.
+  # - If Pre-reboot Dexopt is enabled in asynchronous mode, the command schedules
+  #   an asynchronous job and returns 0 immediately. The job will then run by the
+  #   job scheduler when the device is idle and charging.
+  if infinite_source | pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then
+    # Handled by Pre-reboot Dexopt.
+    exit 0
+  fi
+  echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt."
+else
+  echo "Pre-reboot Dexopt is too old. Fall back to otapreopt."
 fi
-echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt."
 
 if [ "$(/system/bin/otapreopt_chroot --version)" != 2 ]; then
   # We require an updated chroot wrapper that reads dexopt commands from stdin.
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index 61fe316..f3e024c 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -102,7 +102,6 @@
         "libziparchive",
         "liblog",
         "liblogwrap",
-        "libc++fs",
     ],
     product_variables: {
         arc: {
diff --git a/cmds/service/Android.bp b/cmds/service/Android.bp
index 21ac11b..00fd249 100644
--- a/cmds/service/Android.bp
+++ b/cmds/service/Android.bp
@@ -27,7 +27,6 @@
     ],
 
     cflags: [
-        "-DXP_UNIX",
         "-Wall",
         "-Werror",
     ],
@@ -46,7 +45,6 @@
     ],
 
     cflags: [
-        "-DXP_UNIX",
         "-DVENDORSERVICES",
         "-Wall",
         "-Werror",
@@ -65,7 +63,6 @@
     ],
 
     cflags: [
-        "-DXP_UNIX",
         "-Wall",
         "-Werror",
     ],
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 3897197..e5d7b74 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -29,6 +29,7 @@
         "liblog",
         "libutils",
         "libselinux",
+        "libperfetto_c",
     ],
 
     target: {
@@ -48,7 +49,13 @@
             enabled: false,
         },
         vendor: {
-            exclude_shared_libs: ["libvintf"],
+            exclude_shared_libs: [
+                "libvintf",
+                "libperfetto_c",
+            ],
+        },
+        recovery: {
+            exclude_shared_libs: ["libperfetto_c"],
         },
     },
 }
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 95a05cd..1333599 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <binder/BpBinder.h>
 #include <binder/IPCThreadState.h>
@@ -27,6 +28,11 @@
 #include <cutils/multiuser.h>
 #include <thread>
 
+#if !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#endif // !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+
 #ifndef VENDORSERVICEMANAGER
 #include <vintf/VintfObject.h>
 #ifdef __ANDROID_RECOVERY__
@@ -42,6 +48,17 @@
 
 namespace android {
 
+#if defined(VENDORSERVICEMANAGER) || defined(__ANDROID_RECOVERY__)
+#define SM_PERFETTO_TRACE_FUNC(...)
+#else
+
+PERFETTO_TE_CATEGORIES_DEFINE(PERFETTO_SM_CATEGORIES);
+
+#define SM_PERFETTO_TRACE_FUNC(...) \
+    PERFETTO_TE_SCOPED(servicemanager, PERFETTO_TE_SLICE_BEGIN(__func__) __VA_OPT__(, ) __VA_ARGS__)
+
+#endif // !(defined(VENDORSERVICEMANAGER) || defined(__ANDROID_RECOVERY__))
+
 bool is_multiuser_uid_isolated(uid_t uid) {
     uid_t appid = multiuser_get_app_id(uid);
     return appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;
@@ -348,18 +365,24 @@
 }
 
 Status ServiceManager::getService(const std::string& name, sp<IBinder>* outBinder) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     *outBinder = tryGetService(name, true);
     // returns ok regardless of result for legacy reasons
     return Status::ok();
 }
 
 Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     *outBinder = tryGetService(name, false);
     // returns ok regardless of result for legacy reasons
     return Status::ok();
 }
 
 sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     sp<IBinder> out;
@@ -398,6 +421,8 @@
 }
 
 bool isValidServiceName(const std::string& name) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     if (name.size() == 0) return false;
     if (name.size() > 127) return false;
 
@@ -413,6 +438,8 @@
 }
 
 Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     if (multiuser_get_app_id(ctx.uid) >= AID_APP) {
@@ -505,6 +532,8 @@
 }
 
 Status ServiceManager::listServices(int32_t dumpPriority, std::vector<std::string>* outList) {
+    SM_PERFETTO_TRACE_FUNC();
+
     if (!mAccess->canList(mAccess->getCallingContext())) {
         return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
@@ -532,6 +561,8 @@
 
 Status ServiceManager::registerForNotifications(
         const std::string& name, const sp<IServiceCallback>& callback) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
@@ -578,6 +609,8 @@
 }
 Status ServiceManager::unregisterForNotifications(
         const std::string& name, const sp<IServiceCallback>& callback) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
@@ -601,6 +634,8 @@
 }
 
 Status ServiceManager::isDeclared(const std::string& name, bool* outReturn) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
@@ -616,6 +651,8 @@
 }
 
 binder::Status ServiceManager::getDeclaredInstances(const std::string& interface, std::vector<std::string>* outReturn) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("interface", interface.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     std::vector<std::string> allInstances;
@@ -640,6 +677,8 @@
 
 Status ServiceManager::updatableViaApex(const std::string& name,
                                         std::optional<std::string>* outReturn) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
@@ -656,6 +695,8 @@
 
 Status ServiceManager::getUpdatableNames([[maybe_unused]] const std::string& apexName,
                                          std::vector<std::string>* outReturn) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("apexName", apexName.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     std::vector<std::string> apexUpdatableNames;
@@ -674,12 +715,13 @@
     if (outReturn->size() == 0 && apexUpdatableNames.size() != 0) {
         return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
-
     return Status::ok();
 }
 
 Status ServiceManager::getConnectionInfo(const std::string& name,
                                          std::optional<ConnectionInfo>* outReturn) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
@@ -697,6 +739,8 @@
 void ServiceManager::removeRegistrationCallback(const wp<IBinder>& who,
                                     ServiceCallbackMap::iterator* it,
                                     bool* found) {
+    SM_PERFETTO_TRACE_FUNC();
+
     std::vector<sp<IServiceCallback>>& listeners = (*it)->second;
 
     for (auto lit = listeners.begin(); lit != listeners.end();) {
@@ -716,6 +760,8 @@
 }
 
 void ServiceManager::binderDied(const wp<IBinder>& who) {
+    SM_PERFETTO_TRACE_FUNC();
+
     for (auto it = mNameToService.begin(); it != mNameToService.end();) {
         if (who == it->second.binder) {
             // TODO: currently, this entry contains the state also
@@ -758,6 +804,8 @@
 
 Status ServiceManager::registerClientCallback(const std::string& name, const sp<IBinder>& service,
                                               const sp<IClientCallback>& cb) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     if (cb == nullptr) {
         return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Callback null.");
     }
@@ -918,6 +966,8 @@
 }
 
 Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IBinder>& binder) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_ARG_STRING("name", name.c_str()));
+
     if (binder == nullptr) {
         return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Null service.");
     }
@@ -983,6 +1033,7 @@
 }
 
 Status ServiceManager::getServiceDebugInfo(std::vector<ServiceDebugInfo>* outReturn) {
+    SM_PERFETTO_TRACE_FUNC();
     if (!mAccess->canList(mAccess->getCallingContext())) {
         return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
     }
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 3b925a4..1536014 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -20,6 +20,10 @@
 #include <android/os/IClientCallback.h>
 #include <android/os/IServiceCallback.h>
 
+#if !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+#include "perfetto/public/te_category_macros.h"
+#endif // !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+
 #include "Access.h"
 
 namespace android {
@@ -29,6 +33,11 @@
 using os::IServiceCallback;
 using os::ServiceDebugInfo;
 
+#if !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+#define PERFETTO_SM_CATEGORIES(C) C(servicemanager, "servicemanager", "Service Manager category")
+PERFETTO_TE_CATEGORIES_DECLARE(PERFETTO_SM_CATEGORIES);
+#endif // !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+
 class ServiceManager : public os::BnServiceManager, public IBinder::DeathRecipient {
 public:
     ServiceManager(std::unique_ptr<Access>&& access);
diff --git a/cmds/servicemanager/main.cpp b/cmds/servicemanager/main.cpp
index 07908ba..c126e91 100644
--- a/cmds/servicemanager/main.cpp
+++ b/cmds/servicemanager/main.cpp
@@ -26,6 +26,26 @@
 #include "Access.h"
 #include "ServiceManager.h"
 
+#if !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+
+#include <perfetto/public/producer.h>
+#include <perfetto/public/te_category_macros.h>
+#include <perfetto/public/te_macros.h>
+#include <perfetto/public/track_event.h>
+
+namespace android {
+
+static void register_perfetto_te_categories() {
+    struct PerfettoProducerInitArgs perfetto_args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+    perfetto_args.backends = PERFETTO_BACKEND_SYSTEM;
+    PerfettoProducerInit(perfetto_args);
+    PerfettoTeInit();
+    PERFETTO_TE_REGISTER_CATEGORIES(PERFETTO_SM_CATEGORIES);
+}
+} // namespace android
+
+#endif // !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+
 using ::android::Access;
 using ::android::IPCThreadState;
 using ::android::Looper;
@@ -132,6 +152,10 @@
 
     const char* driver = argc == 2 ? argv[1] : "/dev/binder";
 
+#if !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+    android::register_perfetto_te_categories();
+#endif // !defined(VENDORSERVICEMANAGER) && !defined(__ANDROID_RECOVERY__)
+
     LOG(INFO) << "Starting sm instance on " << driver;
 
     sp<ProcessState> ps = ProcessState::initWithDriver(driver);
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 0300f8c..766c768 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -418,6 +418,14 @@
     defaults: ["frameworks_native_data_etc_defaults"],
 }
 
+// installed in system
+prebuilt_etc {
+    name: "android.software.preview_sdk.prebuilt.xml",
+    relative_install_path: "permissions",
+    src: "android.software.preview_sdk.xml",
+    filename_from_src: true,
+}
+
 prebuilt_etc {
     name: "android.software.sip.voip.prebuilt.xml",
     src: "android.software.sip.voip.xml",
@@ -460,6 +468,22 @@
     defaults: ["frameworks_native_data_etc_defaults"],
 }
 
+// installed in system
+prebuilt_etc {
+    name: "android.software.webview.prebuilt.xml",
+    relative_install_path: "permissions",
+    src: "android.software.webview.xml",
+    filename_from_src: true,
+}
+
+// installed in system
+prebuilt_etc {
+    name: "android.software.window_magnification.prebuilt.xml",
+    relative_install_path: "permissions",
+    src: "android.software.window_magnification.xml",
+    filename_from_src: true,
+}
+
 prebuilt_etc {
     name: "aosp_excluded_hardware.prebuilt.xml",
     src: "aosp_excluded_hardware.xml",
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
new file mode 100644
index 0000000..945e720
--- /dev/null
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- This is the standard set of features for devices to support Telephony Satellite API. -->
+<permissions>
+    <feature name="android.hardware.telephony" />
+    <feature name="android.hardware.telephony.satellite" />
+</permissions>
\ No newline at end of file
diff --git a/include/android/input.h b/include/android/input.h
index fec56f0..ee98d7a 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -1002,7 +1002,6 @@
  * Keyboard types.
  *
  * Refer to the documentation on android.view.InputDevice for more details.
- * Note: When adding a new keyboard type here InputDeviceInfo::setKeyboardType needs to be updated.
  */
 enum {
     /** none */
diff --git a/include/android/system_fonts.h b/include/android/system_fonts.h
index 94484ea..2d3a214 100644
--- a/include/android/system_fonts.h
+++ b/include/android/system_fonts.h
@@ -31,27 +31,27 @@
  *
  * \code{.cpp}
  *   ASystemFontIterator* iterator = ASystemFontIterator_open();
- *   ASystemFont* font = NULL;
+ *   AFont* font = NULL;
  *
  *   while ((font = ASystemFontIterator_next(iterator)) != nullptr) {
  *       // Look if the font is your desired one.
- *       if (ASystemFont_getWeight(font) == 400 && !ASystemFont_isItalic(font)
- *           && ASystemFont_getLocale(font) == NULL) {
+ *       if (AFont_getWeight(font) == 400 && !AFont_isItalic(font)
+ *           && AFont_getLocale(font) == NULL) {
  *           break;
  *       }
- *       ASystemFont_close(font);
+ *       AFont_close(font);
  *   }
  *   ASystemFontIterator_close(iterator);
  *
- *   int fd = open(ASystemFont_getFontFilePath(font), O_RDONLY);
- *   int collectionIndex = ASystemFont_getCollectionINdex(font);
+ *   int fd = open(AFont_getFontFilePath(font), O_RDONLY | O_CLOEXEC);
+ *   int collectionIndex = AFont_getCollectionIndex(font);
  *   std::vector<std::pair<uint32_t, float>> variationSettings;
- *   for (size_t i = 0; i < ASystemFont_getAxisCount(font); ++i) {
+ *   for (size_t i = 0; i < AFont_getAxisCount(font); ++i) {
  *       variationSettings.push_back(std::make_pair(
- *           ASystemFont_getAxisTag(font, i),
- *           ASystemFont_getAxisValue(font, i)));
+ *           AFont_getAxisTag(font, i),
+ *           AFont_getAxisValue(font, i)));
  *   }
- *   ASystemFont_close(font);
+ *   AFont_close(font);
  *
  *   // Use this font for your text rendering engine.
  *
@@ -99,7 +99,7 @@
 /**
  * Create a system font iterator.
  *
- * Use ASystemFont_close() to close the iterator.
+ * Use ASystemFontIterator_close() to close the iterator.
  *
  * Available since API level 29.
  *
@@ -123,7 +123,7 @@
  *
  * \param iterator an iterator for the system fonts. Passing NULL is not allowed.
  * \return a font. If no more font is available, returns nullptr. You need to release the returned
- *         font by ASystemFont_close when it is no longer needed.
+ *         font with AFont_close() when it is no longer needed.
  */
 AFont* _Nullable ASystemFontIterator_next(ASystemFontIterator* _Nonnull iterator) __INTRODUCED_IN(29);
 
diff --git a/include/ftl/expected.h b/include/ftl/expected.h
index 57448dc..7e765c5 100644
--- a/include/ftl/expected.h
+++ b/include/ftl/expected.h
@@ -69,6 +69,36 @@
     exp_.value();                                                         \
   })
 
+// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short),
+// FTL_EXPECT unwraps T out of R, or bails out of the enclosing function F if R has an error E.
+// While FTL_TRY bails out with R, FTL_EXPECT bails out with E, which is useful when F does not
+// need to propagate R because T is not relevant to the caller.
+//
+// Example usage:
+//
+//   using StringExp = ftl::Expected<std::string, std::errc>;
+//
+//   std::errc repeat(StringExp exp, std::string& out) {
+//     const std::string str = FTL_EXPECT(exp);
+//     out = str + str;
+//     return std::errc::operation_in_progress;
+//   }
+//
+//   std::string str;
+//   assert(std::errc::operation_in_progress == repeat(StringExp("ha"s), str));
+//   assert("haha"s == str);
+//   assert(std::errc::bad_message == repeat(ftl::Unexpected(std::errc::bad_message), str));
+//   assert("haha"s == str);
+//
+#define FTL_EXPECT(expr)              \
+  ({                                  \
+    auto exp_ = (expr);               \
+    if (!exp_.has_value()) {          \
+      return std::move(exp_.error()); \
+    }                                 \
+    exp_.value();                     \
+  })
+
 namespace android::ftl {
 
 // Superset of base::expected<T, E> with monadic operations.
diff --git a/include/ftl/function.h b/include/ftl/function.h
index 3538ca4..bda5b75 100644
--- a/include/ftl/function.h
+++ b/include/ftl/function.h
@@ -123,7 +123,7 @@
 //   // Create a typedef to give a more meaningful name and bound the size.
 //   using MyFunction = ftl::Function<int(std::string_view), 2>;
 //   int* ptr = nullptr;
-//   auto f1 = MyFunction::make_function(
+//   auto f1 = MyFunction::make(
 //       [cls = &cls, ptr](std::string_view sv) {
 //           return cls->on_string(ptr, sv);
 //       });
diff --git a/include/input/Input.h b/include/input/Input.h
index 3ca9c19..456977b 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -25,6 +25,7 @@
 #include <android/input.h>
 #ifdef __linux__
 #include <android/os/IInputConstants.h>
+#include <android/os/MotionEventFlag.h>
 #endif
 #include <android/os/PointerIconType.h>
 #include <math.h>
@@ -69,15 +70,17 @@
      * actual intent.
      */
     AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+            static_cast<int32_t>(android::os::MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED),
+
     AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING,
+            static_cast<int32_t>(android::os::MotionEventFlag::HOVER_EXIT_PENDING),
+
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
      * provides a hint to the GestureDetector to not apply any touch slop.
      */
     AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_GENERATED_GESTURE),
 
     /**
      * This flag indicates that the event will not cause a focus change if it is directed to an
@@ -86,19 +89,31 @@
      * into focus.
      */
     AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+            static_cast<int32_t>(android::os::MotionEventFlag::NO_FOCUS_CHANGE),
 
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_ACCESSIBILITY_EVENT),
 
     AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS,
+            static_cast<int32_t>(android::os::MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS),
 
     /* Motion event is inconsistent with previously sent motion events. */
-    AMOTION_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
+    AMOTION_EVENT_FLAG_TAINTED = static_cast<int32_t>(android::os::MotionEventFlag::TAINTED),
+
+    /** Private flag, not used in Java. */
+    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+            static_cast<int32_t>(android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION),
+
+    /** Private flag, not used in Java. */
+    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = static_cast<int32_t>(
+            android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION),
+
+    /** Mask for all private flags that are not used in Java. */
+    AMOTION_EVENT_PRIVATE_FLAG_MASK = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
 };
 
 /**
@@ -209,8 +224,12 @@
  * Transform an angle on the x-y plane. An angle of 0 radians corresponds to "north" or
  * pointing upwards in the negative Y direction, a positive angle points towards the right, and a
  * negative angle points towards the left.
+ *
+ * If the angle represents a direction that needs to be preserved, set isDirectional to true to get
+ * an output range of [-pi, pi]. If the angle's direction does not need to be preserved, set
+ * isDirectional to false to get an output range of [-pi/2, pi/2].
  */
-float transformAngle(const ui::Transform& transform, float angleRadians);
+float transformAngle(const ui::Transform& transform, float angleRadians, bool isDirectional);
 
 /**
  * The type of the InputEvent.
@@ -258,6 +277,16 @@
     ftl_last = VIRTUAL,
 };
 
+/**
+ * The keyboard type. This should have 1:1 correspondence with the values of anonymous enum
+ * defined in input.h
+ */
+enum class KeyboardType {
+    NONE = AINPUT_KEYBOARD_TYPE_NONE,
+    NON_ALPHABETIC = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+    ALPHABETIC = AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+};
+
 bool isStylusToolType(ToolType toolType);
 
 struct PointerProperties;
@@ -462,8 +491,6 @@
     // axes, however the window scaling will not.
     void scale(float globalScale, float windowXScale, float windowYScale);
 
-    void transform(const ui::Transform& transform);
-
     inline float getX() const {
         return getAxisValue(AMOTION_EVENT_AXIS_X);
     }
@@ -930,10 +957,12 @@
     // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods
     // are used to apply these transformations for different axes.
     static vec2 calculateTransformedXY(uint32_t source, const ui::Transform&, const vec2& xy);
-    static float calculateTransformedAxisValue(int32_t axis, uint32_t source, const ui::Transform&,
-                                               const PointerCoords&);
-    static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&,
-                                                    const PointerCoords&);
+    static float calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags,
+                                               const ui::Transform&, const PointerCoords&);
+    static void calculateTransformedCoordsInPlace(PointerCoords& coords, uint32_t source,
+                                                  int32_t flags, const ui::Transform&);
+    static PointerCoords calculateTransformedCoords(uint32_t source, int32_t flags,
+                                                    const ui::Transform&, const PointerCoords&);
     // The rounding precision for transformed motion events.
     static constexpr float ROUNDING_PRECISION = 0.001f;
 
diff --git a/include/input/KeyboardClassifier.h b/include/input/KeyboardClassifier.h
new file mode 100644
index 0000000..457d474
--- /dev/null
+++ b/include/input/KeyboardClassifier.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+
+#include "rust/cxx.h"
+
+namespace android {
+
+namespace input {
+namespace keyboardClassifier {
+struct KeyboardClassifier;
+}
+} // namespace input
+
+/*
+ * Keyboard classifier to classify keyboard into alphabetic and non-alphabetic keyboards
+ */
+class KeyboardClassifier {
+public:
+    KeyboardClassifier();
+    /**
+     * Get the type of keyboard that the classifier currently believes the device to be.
+     */
+    KeyboardType getKeyboardType(DeviceId deviceId);
+    void notifyKeyboardChanged(DeviceId deviceId, const InputDeviceIdentifier& identifier,
+                               uint32_t deviceClasses);
+    void processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState);
+
+private:
+    std::optional<rust::Box<android::input::keyboardClassifier::KeyboardClassifier>>
+            mRustClassifier;
+    std::unordered_map<DeviceId, KeyboardType> mKeyboardTypeMap;
+};
+
+} // namespace android
diff --git a/libs/adbd_auth/adbd_auth.cpp b/libs/adbd_auth/adbd_auth.cpp
index ebc74fb..78896ed 100644
--- a/libs/adbd_auth/adbd_auth.cpp
+++ b/libs/adbd_auth/adbd_auth.cpp
@@ -365,7 +365,7 @@
                         if (event.events & EPOLLIN) {
                             int rc = TEMP_FAILURE_RETRY(read(framework_fd_.get(), buf, sizeof(buf)));
                             if (rc == -1) {
-                                LOG(FATAL) << "adbd_auth: failed to read from framework fd";
+                                PLOG(FATAL) << "adbd_auth: failed to read from framework fd";
                             } else if (rc == 0) {
                                 LOG(INFO) << "adbd_auth: hit EOF on framework fd";
                                 std::lock_guard<std::mutex> lock(mutex_);
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 090e35b..bd6a08e 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_virtualization",
 }
 
 cc_library_headers {
@@ -28,6 +29,7 @@
     recovery_available: true,
     host_supported: true,
     native_bridge_supported: true,
+    cmake_snapshot_supported: true,
 
     header_libs: [
         "libbinder_headers_platform_shared",
@@ -83,6 +85,132 @@
     },
 }
 
+cc_cmake_snapshot {
+    name: "binder_sdk",
+    modules: [
+        "libbinder_sdk",
+        "libbinder_sdk_single_threaded",
+        "libbinder_ndk_sdk",
+        "googletest_cmake",
+
+        "binderRpcTestNoKernel",
+        "binderRpcTestSingleThreadedNoKernel",
+        "binderRpcWireProtocolTest",
+    ],
+    prebuilts: [
+        // to enable arm64 host support, build with musl - e.g. on aosp_cf_arm64_phone
+        "aidl",
+        "libc++",
+    ],
+    include_sources: true,
+    cflags: [
+        "-DNDEBUG",
+        "-DBINDER_ENABLE_LIBLOG_ASSERT",
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+        "-DBINDER_DISABLE_BLOB",
+        "-DBINDER_NO_LIBBASE",
+        "-DBINDER_NO_KERNEL_IPC_TESTING",
+
+        // from Soong's global.go commonGlobalCflags and noOverrideGlobalCflags
+        "-Wno-c99-designator",
+        "-Wno-missing-field-initializers",
+
+        // warnings that only pop up on gcc
+        "-Wno-unknown-pragmas", // "pragma clang"
+        "-Wno-attributes", // attributes on compound-statements
+        "-Wno-psabi", // reminders about old ABI changes
+    ],
+    cflags_ignored: [
+        // gcc requires all header constexprs to be used in all dependent compilatinon units
+        "-Wunused-const-variable",
+    ],
+    library_mapping: [
+        {
+            android_name: "libssl",
+            mapped_name: "ssl",
+            package_pregenerated: "external/boringssl",
+        },
+        {
+            android_name: "libcrypto",
+            mapped_name: "crypto",
+            package_pregenerated: "external/boringssl",
+        },
+        {
+            android_name: "libgtest",
+            mapped_name: "GTest::gtest",
+            package_pregenerated: "external/googletest",
+        },
+        {
+            android_name: "libgtest_main",
+            mapped_name: "GTest::gtest_main",
+            package_pregenerated: "external/googletest",
+        },
+        {
+            android_name: "googletest_cmake",
+            package_pregenerated: "external/googletest",
+        },
+
+        // use libbinder_sdk and friends instead of full Android's libbinder
+        {
+            android_name: "libbinder_rpc_no_kernel",
+            mapped_name: "android::libbinder_sdk",
+        },
+        {
+            android_name: "libbinder_rpc_single_threaded_no_kernel",
+            mapped_name: "android::libbinder_sdk_single_threaded",
+        },
+        {
+            android_name: "libbinder_headers",
+            mapped_name: "android::libbinder_headers_base",
+        },
+        {
+            android_name: "libbinder",
+            mapped_name: "android::libbinder_sdk",
+        },
+        {
+            android_name: "libbinder_ndk",
+            mapped_name: "android::libbinder_ndk_sdk",
+        },
+        {
+            android_name: "liblog",
+            mapped_name: "android::liblog_stub",
+        },
+
+        // explicitly included by Binder tests, but not needed outside of Android
+        {
+            android_name: "libbase",
+        },
+        {
+            android_name: "libcutils",
+        },
+        {
+            android_name: "libutils",
+        },
+
+        // disable tests that don't work outside of Android yet
+        {
+            android_name: "binder_rpc_test_service",
+        },
+        {
+            android_name: "binder_rpc_test_service_single_threaded",
+        },
+
+        // trusty mocks are artificially triggered and not needed outside of Android build
+        {
+            android_name: "libbinder_on_trusty_mock",
+        },
+        {
+            android_name: "libbinder_ndk_on_trusty_mock",
+        },
+        {
+            android_name: "binderRpcTestService_on_trusty_mock",
+        },
+        {
+            android_name: "binderRpcTest_on_trusty_mock",
+        },
+    ],
+}
+
 // These interfaces are android-specific implementation unrelated to binder
 // transport itself and should be moved to AIDL or in domain-specific libs.
 //
@@ -126,6 +254,9 @@
     header_libs: [
         "libbinder_headers_base",
     ],
+    export_header_lib_headers: [
+        "libbinder_headers_base",
+    ],
 
     cflags: [
         "-Wextra",
@@ -137,6 +268,21 @@
         "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
+
+    target: {
+        bionic: {
+            // Hide symbols by default and set the BUILDING_LIBBINDER macro so that
+            // the code knows to export them.
+            //
+            // Only enabled on bionic builds, where RTTI is disabled, because
+            // it is failing to export required typeinfo symbols.
+            // TODO: b/341341056 - Find a solution for non-bionic builds.
+            cflags: [
+                "-fvisibility=hidden",
+                "-DBUILDING_LIBBINDER",
+            ],
+        },
+    },
 }
 
 cc_defaults {
@@ -369,6 +515,7 @@
     double_loadable: true,
     // TODO(b/153609531): remove when no longer needed.
     native_bridge_supported: true,
+    cmake_snapshot_supported: false,
 
     // libbinder does not offer a stable wire protocol.
     // if a second copy of it is installed, then it may break after security
@@ -408,16 +555,8 @@
     afdo: true,
 }
 
-cc_library_host_shared {
-    name: "libbinder_sdk",
-
-    defaults: [
-        "libbinder_common_defaults",
-    ],
-
-    shared_libs: [
-        "libutils_binder_sdk",
-    ],
+cc_defaults {
+    name: "binder_sdk_defaults",
 
     cflags: [
         "-DBINDER_ENABLE_LIBLOG_ASSERT",
@@ -429,6 +568,21 @@
     header_libs: [
         "liblog_stub",
     ],
+}
+
+cc_defaults {
+    name: "libbinder_sdk_defaults",
+
+    cmake_snapshot_supported: true,
+
+    defaults: [
+        "libbinder_common_defaults",
+        "binder_sdk_defaults",
+    ],
+
+    shared_libs: [
+        "libutils_binder_sdk",
+    ],
 
     srcs: [
         "OS_non_android_linux.cpp",
@@ -446,6 +600,19 @@
     },
 }
 
+cc_library_host_shared {
+    name: "libbinder_sdk",
+    defaults: ["libbinder_sdk_defaults"],
+}
+
+cc_library_host_shared {
+    name: "libbinder_sdk_single_threaded",
+    defaults: ["libbinder_sdk_defaults"],
+    cflags: [
+        "-DBINDER_RPC_SINGLE_THREADED",
+    ],
+}
+
 cc_library {
     name: "libbinder_rpc_no_kernel",
     vendor_available: true,
@@ -535,6 +702,7 @@
     defaults: ["libbinder_tls_shared_deps"],
     vendor_available: true,
     host_supported: true,
+    cmake_snapshot_supported: true,
 
     header_libs: [
         "libbinder_headers",
@@ -745,7 +913,4 @@
         "libutils",
         "android.debug_aidl-cpp",
     ],
-    static_libs: [
-        "libc++fs",
-    ],
 }
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 7a855e1..6594aa6 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -15,7 +15,6 @@
  */
 
 #define LOG_TAG "BpBinder"
-#define ATRACE_TAG ATRACE_TAG_AIDL
 //#define LOG_NDEBUG 0
 
 #include <binder/BpBinder.h>
@@ -24,15 +23,10 @@
 #include <binder/IResultReceiver.h>
 #include <binder/RpcSession.h>
 #include <binder/Stability.h>
+#include <binder/Trace.h>
 
 #include <stdio.h>
 
-#ifndef __TRUSTY__
-#include <cutils/trace.h>
-#else
-#define ATRACE_INT(...)
-#endif
-
 #include "BuildFlags.h"
 #include "file.h"
 
@@ -216,7 +210,7 @@
         sTrackingMap[trackedUid]++;
     }
     uint32_t numProxies = sBinderProxyCount.fetch_add(1, std::memory_order_relaxed);
-    ATRACE_INT("binder_proxies", numProxies);
+    binder::os::trace_int(ATRACE_TAG_AIDL, "binder_proxies", numProxies);
     uint32_t numLastWarned = sBinderProxyCountWarned.load(std::memory_order_relaxed);
     uint32_t numNextWarn = numLastWarned + kBinderProxyCountWarnInterval;
     if (numProxies >= numNextWarn) {
@@ -640,8 +634,8 @@
             }
         }
     }
-    [[maybe_unused]] uint32_t numProxies = --sBinderProxyCount;
-    ATRACE_INT("binder_proxies", numProxies);
+    uint32_t numProxies = --sBinderProxyCount;
+    binder::os::trace_int(ATRACE_TAG_AIDL, "binder_proxies", numProxies);
     if (ipc) {
         ipc->expungeHandle(binderHandle(), this);
         ipc->decWeakHandle(binderHandle());
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index ef96f80..c3bbdba 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -613,16 +613,19 @@
 
 void IPCThreadState::blockUntilThreadAvailable()
 {
-    pthread_mutex_lock(&mProcess->mThreadCountLock);
-    mProcess->mWaitingForThreads++;
-    while (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads) {
-        ALOGW("Waiting for thread to be free. mExecutingThreadsCount=%lu mMaxThreads=%lu\n",
-                static_cast<unsigned long>(mProcess->mExecutingThreadsCount),
-                static_cast<unsigned long>(mProcess->mMaxThreads));
-        pthread_cond_wait(&mProcess->mThreadCountDecrement, &mProcess->mThreadCountLock);
-    }
-    mProcess->mWaitingForThreads--;
-    pthread_mutex_unlock(&mProcess->mThreadCountLock);
+    std::unique_lock lock_guard_(mProcess->mOnThreadAvailableLock);
+    mProcess->mOnThreadAvailableWaiting++;
+    mProcess->mOnThreadAvailableCondVar.wait(lock_guard_, [&] {
+        size_t max = mProcess->mMaxThreads;
+        size_t cur = mProcess->mExecutingThreadsCount;
+        if (cur < max) {
+            return true;
+        }
+        ALOGW("Waiting for thread to be free. mExecutingThreadsCount=%zu mMaxThreads=%zu\n", cur,
+              max);
+        return false;
+    });
+    mProcess->mOnThreadAvailableWaiting--;
 }
 
 status_t IPCThreadState::getAndExecuteCommand()
@@ -642,34 +645,33 @@
             ALOGI("%s", message.c_str());
         }
 
-        pthread_mutex_lock(&mProcess->mThreadCountLock);
-        mProcess->mExecutingThreadsCount++;
-        if (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads &&
-                mProcess->mStarvationStartTimeMs == 0) {
-            mProcess->mStarvationStartTimeMs = uptimeMillis();
+        size_t newThreadsCount = mProcess->mExecutingThreadsCount.fetch_add(1) + 1;
+        if (newThreadsCount >= mProcess->mMaxThreads) {
+            int64_t expected = 0;
+            mProcess->mStarvationStartTimeMs.compare_exchange_strong(expected, uptimeMillis());
         }
-        pthread_mutex_unlock(&mProcess->mThreadCountLock);
 
         result = executeCommand(cmd);
 
-        pthread_mutex_lock(&mProcess->mThreadCountLock);
-        mProcess->mExecutingThreadsCount--;
-        if (mProcess->mExecutingThreadsCount < mProcess->mMaxThreads &&
-                mProcess->mStarvationStartTimeMs != 0) {
-            int64_t starvationTimeMs = uptimeMillis() - mProcess->mStarvationStartTimeMs;
-            if (starvationTimeMs > 100) {
-                ALOGE("binder thread pool (%zu threads) starved for %" PRId64 " ms",
-                      mProcess->mMaxThreads, starvationTimeMs);
+        size_t maxThreads = mProcess->mMaxThreads;
+        newThreadsCount = mProcess->mExecutingThreadsCount.fetch_sub(1) - 1;
+        if (newThreadsCount < maxThreads) {
+            size_t starvationStartTimeMs = mProcess->mStarvationStartTimeMs.exchange(0);
+            if (starvationStartTimeMs != 0) {
+                int64_t starvationTimeMs = uptimeMillis() - starvationStartTimeMs;
+                if (starvationTimeMs > 100) {
+                    ALOGE("binder thread pool (%zu threads) starved for %" PRId64 " ms", maxThreads,
+                          starvationTimeMs);
+                }
             }
-            mProcess->mStarvationStartTimeMs = 0;
         }
 
         // Cond broadcast can be expensive, so don't send it every time a binder
         // call is processed. b/168806193
-        if (mProcess->mWaitingForThreads > 0) {
-            pthread_cond_broadcast(&mProcess->mThreadCountDecrement);
+        if (mProcess->mOnThreadAvailableWaiting > 0) {
+            std::lock_guard lock_guard_(mProcess->mOnThreadAvailableLock);
+            mProcess->mOnThreadAvailableCondVar.notify_all();
         }
-        pthread_mutex_unlock(&mProcess->mThreadCountLock);
     }
 
     return result;
@@ -727,10 +729,9 @@
 
 void IPCThreadState::joinThreadPool(bool isMain)
 {
-    LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
-    pthread_mutex_lock(&mProcess->mThreadCountLock);
+    LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(),
+                   getpid());
     mProcess->mCurrentThreads++;
-    pthread_mutex_unlock(&mProcess->mThreadCountLock);
     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
 
     mIsLooper = true;
@@ -758,13 +759,11 @@
     mOut.writeInt32(BC_EXIT_LOOPER);
     mIsLooper = false;
     talkWithDriver(false);
-    pthread_mutex_lock(&mProcess->mThreadCountLock);
-    LOG_ALWAYS_FATAL_IF(mProcess->mCurrentThreads == 0,
-                        "Threadpool thread count = 0. Thread cannot exist and exit in empty "
-                        "threadpool\n"
+    size_t oldCount = mProcess->mCurrentThreads.fetch_sub(1);
+    LOG_ALWAYS_FATAL_IF(oldCount == 0,
+                        "Threadpool thread count underflowed. Thread cannot exist and exit in "
+                        "empty threadpool\n"
                         "Misconfiguration. Increase threadpool max threads configuration\n");
-    mProcess->mCurrentThreads--;
-    pthread_mutex_unlock(&mProcess->mThreadCountLock);
 }
 
 status_t IPCThreadState::setupPolling(int* fd)
@@ -776,9 +775,7 @@
     mOut.writeInt32(BC_ENTER_LOOPER);
     flushCommands();
     *fd = mProcess->mDriverFD;
-    pthread_mutex_lock(&mProcess->mThreadCountLock);
     mProcess->mCurrentThreads++;
-    pthread_mutex_unlock(&mProcess->mThreadCountLock);
     return 0;
 }
 
@@ -1027,6 +1024,7 @@
             goto finish;
 
         case BR_FROZEN_REPLY:
+            ALOGW("Transaction failed because process frozen.");
             err = FAILED_TRANSACTION;
             goto finish;
 
@@ -1578,8 +1576,8 @@
     }
 #endif
 
-    ALOGE_IF(ee.command != BR_OK, "Binder transaction failure: %d/%d/%d",
-             ee.id, ee.command, ee.param);
+    ALOGE_IF(ee.command != BR_OK, "Binder transaction failure. id: %d, BR_*: %d, error: %d (%s)",
+             ee.id, ee.command, ee.param, strerror(-ee.param));
 }
 
 void IPCThreadState::freeBuffer(const uint8_t* data, size_t /*dataSize*/,
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 5703eb7..04869a1 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -26,6 +26,7 @@
 
 LIBBINDER_EXPORTED void trace_begin(uint64_t tag, const char* name);
 LIBBINDER_EXPORTED void trace_end(uint64_t tag);
+LIBBINDER_EXPORTED void trace_int(uint64_t tag, const char* name, int32_t value);
 
 status_t setNonBlocking(borrowed_fd fd);
 
diff --git a/libs/binder/OS_android.cpp b/libs/binder/OS_android.cpp
index 1eace85..893ee15 100644
--- a/libs/binder/OS_android.cpp
+++ b/libs/binder/OS_android.cpp
@@ -44,6 +44,10 @@
     atrace_end(tag);
 }
 
+void trace_int(uint64_t tag, const char* name, int32_t value) {
+    atrace_int(tag, name, value);
+}
+
 } // namespace os
 
 // Legacy trace symbol. To be removed once all of downstream rebuilds.
diff --git a/libs/binder/OS_non_android_linux.cpp b/libs/binder/OS_non_android_linux.cpp
index 01f3fe0..0c64eb6 100644
--- a/libs/binder/OS_non_android_linux.cpp
+++ b/libs/binder/OS_non_android_linux.cpp
@@ -39,6 +39,8 @@
 
 void trace_end(uint64_t) {}
 
+void trace_int(uint64_t, const char*, int32_t) {}
+
 uint64_t GetThreadId() {
     return syscall(__NR_gettid);
 }
diff --git a/libs/binder/PersistableBundle.cpp b/libs/binder/PersistableBundle.cpp
index 1504715..5b157cc 100644
--- a/libs/binder/PersistableBundle.cpp
+++ b/libs/binder/PersistableBundle.cpp
@@ -82,13 +82,12 @@
          }                                                               \
     }
 
-#define RETURN_IF_ENTRY_ERASED(map, key)                                 \
-    {                                                                    \
-        size_t num_erased = (map).erase(key);                            \
-        if (num_erased) {                                                \
-            ALOGE("Failed at %s:%d (%s)", __FILE__, __LINE__, __func__); \
-            return num_erased;                                           \
-         }                                                               \
+#define RETURN_IF_ENTRY_ERASED(map, key)      \
+    {                                         \
+        size_t num_erased = (map).erase(key); \
+        if (num_erased) {                     \
+            return num_erased;                \
+        }                                     \
     }
 
 status_t PersistableBundle::writeToParcel(Parcel* parcel) const {
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index fb2781b..ad5a6b3 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -407,9 +407,7 @@
         ALOGV("Spawning new pooled thread, name=%s\n", name.c_str());
         sp<Thread> t = sp<PoolThread>::make(isMain);
         t->run(name.c_str());
-        pthread_mutex_lock(&mThreadCountLock);
         mKernelStartedThreads++;
-        pthread_mutex_unlock(&mThreadCountLock);
     }
     // TODO: if startThreadPool is called on another thread after the process
     // starts up, the kernel might think that it already requested those
@@ -432,19 +430,19 @@
 }
 
 size_t ProcessState::getThreadPoolMaxTotalThreadCount() const {
-    pthread_mutex_lock(&mThreadCountLock);
-    auto detachGuard = make_scope_guard([&]() { pthread_mutex_unlock(&mThreadCountLock); });
-
     if (mThreadPoolStarted) {
-        LOG_ALWAYS_FATAL_IF(mKernelStartedThreads > mMaxThreads + 1,
-                            "too many kernel-started threads: %zu > %zu + 1", mKernelStartedThreads,
-                            mMaxThreads);
+        size_t kernelStarted = mKernelStartedThreads;
+        size_t max = mMaxThreads;
+        size_t current = mCurrentThreads;
+
+        LOG_ALWAYS_FATAL_IF(kernelStarted > max + 1,
+                            "too many kernel-started threads: %zu > %zu + 1", kernelStarted, max);
 
         // calling startThreadPool starts a thread
         size_t threads = 1;
 
         // the kernel is configured to start up to mMaxThreads more threads
-        threads += mMaxThreads;
+        threads += max;
 
         // Users may call IPCThreadState::joinThreadPool directly. We don't
         // currently have a way to count this directly (it could be added by
@@ -454,8 +452,8 @@
         // in IPCThreadState, temporarily forget about the extra join threads.
         // This is okay, because most callers of this method only care about
         // having 0, 1, or more threads.
-        if (mCurrentThreads > mKernelStartedThreads) {
-            threads += mCurrentThreads - mKernelStartedThreads;
+        if (current > kernelStarted) {
+            threads += current - kernelStarted;
         }
 
         return threads;
@@ -463,10 +461,9 @@
 
     // must not be initialized or maybe has poll thread setup, we
     // currently don't track this in libbinder
-    LOG_ALWAYS_FATAL_IF(mKernelStartedThreads != 0,
-                        "Expecting 0 kernel started threads but have"
-                        " %zu",
-                        mKernelStartedThreads);
+    size_t kernelStarted = mKernelStartedThreads;
+    LOG_ALWAYS_FATAL_IF(kernelStarted != 0, "Expecting 0 kernel started threads but have %zu",
+                        kernelStarted);
     return mCurrentThreads;
 }
 
@@ -516,22 +513,23 @@
     return mDriverName;
 }
 
-static unique_fd open_driver(const char* driver) {
+static unique_fd open_driver(const char* driver, String8* error) {
     auto fd = unique_fd(open(driver, O_RDWR | O_CLOEXEC));
     if (!fd.ok()) {
-        PLOGE("Opening '%s' failed", driver);
+        error->appendFormat("%d (%s) Opening '%s' failed", errno, strerror(errno), driver);
         return {};
     }
     int vers = 0;
     int result = ioctl(fd.get(), BINDER_VERSION, &vers);
     if (result == -1) {
-        PLOGE("Binder ioctl to obtain version failed");
+        error->appendFormat("%d (%s) Binder ioctl to obtain version failed", errno,
+                            strerror(errno));
         return {};
     }
     if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
-        ALOGE("Binder driver protocol(%d) does not match user space protocol(%d)! "
-              "ioctl() return value: %d",
-              vers, BINDER_CURRENT_PROTOCOL_VERSION, result);
+        error->appendFormat("Binder driver protocol(%d) does not match user space protocol(%d)! "
+                            "ioctl() return value: %d",
+                            vers, BINDER_CURRENT_PROTOCOL_VERSION, result);
         return {};
     }
     size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
@@ -553,10 +551,7 @@
       : mDriverName(String8(driver)),
         mDriverFD(-1),
         mVMStart(MAP_FAILED),
-        mThreadCountLock(PTHREAD_MUTEX_INITIALIZER),
-        mThreadCountDecrement(PTHREAD_COND_INITIALIZER),
         mExecutingThreadsCount(0),
-        mWaitingForThreads(0),
         mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
         mCurrentThreads(0),
         mKernelStartedThreads(0),
@@ -565,7 +560,8 @@
         mThreadPoolStarted(false),
         mThreadPoolSeq(1),
         mCallRestriction(CallRestriction::NONE) {
-    unique_fd opened = open_driver(driver);
+    String8 error;
+    unique_fd opened = open_driver(driver, &error);
 
     if (opened.ok()) {
         // mmap the binder, providing a chunk of virtual address space to receive transactions.
@@ -580,8 +576,9 @@
     }
 
 #ifdef __ANDROID__
-    LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating.",
-                        driver);
+    LOG_ALWAYS_FATAL_IF(!opened.ok(),
+                        "Binder driver '%s' could not be opened. Error: %s. Terminating.",
+                        error.c_str(), driver);
 #endif
 
     if (opened.ok()) {
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index a466638..11898a0 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -23,6 +23,7 @@
 
 #include <pthread.h>
 
+#include <atomic>
 #include <mutex>
 
 // ---------------------------------------------------------------------------
@@ -162,22 +163,21 @@
     int mDriverFD;
     void* mVMStart;
 
-    // Protects thread count and wait variables below.
-    mutable pthread_mutex_t mThreadCountLock;
-    // Broadcast whenever mWaitingForThreads > 0
-    pthread_cond_t mThreadCountDecrement;
+    mutable std::mutex mOnThreadAvailableLock;
+    std::condition_variable mOnThreadAvailableCondVar;
+    // Number of threads waiting on `mOnThreadAvailableCondVar`.
+    std::atomic_int64_t mOnThreadAvailableWaiting = 0;
+
     // Number of binder threads current executing a command.
-    size_t mExecutingThreadsCount;
-    // Number of threads calling IPCThreadState::blockUntilThreadAvailable()
-    size_t mWaitingForThreads;
+    std::atomic_size_t mExecutingThreadsCount;
     // Maximum number of lazy threads to be started in the threadpool by the kernel.
-    size_t mMaxThreads;
+    std::atomic_size_t mMaxThreads;
     // Current number of threads inside the thread pool.
-    size_t mCurrentThreads;
+    std::atomic_size_t mCurrentThreads;
     // Current number of pooled threads inside the thread pool.
-    size_t mKernelStartedThreads;
+    std::atomic_size_t mKernelStartedThreads;
     // Time when thread pool was emptied
-    int64_t mStarvationStartTimeMs;
+    std::atomic_int64_t mStarvationStartTimeMs;
 
     mutable std::mutex mLock; // protects everything below.
 
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index e8d9829..40102bb 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -129,9 +129,9 @@
     setupUnixDomainSocketBootstrapClient(binder::unique_fd bootstrap);
 
     /**
-     * Connects to an RPC server at the CVD & port.
+     * Connects to an RPC server at the CID & port.
      */
-    [[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockClient(unsigned int cvd, unsigned int port);
+    [[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockClient(unsigned int cid, unsigned int port);
 
     /**
      * Connects to an RPC server at the given address and port.
diff --git a/libs/binder/include/binder/Trace.h b/libs/binder/include/binder/Trace.h
index 268157e..2f450cb 100644
--- a/libs/binder/include/binder/Trace.h
+++ b/libs/binder/include/binder/Trace.h
@@ -41,6 +41,7 @@
 // libcutils/libutils
 void trace_begin(uint64_t tag, const char* name);
 void trace_end(uint64_t tag);
+void trace_int(uint64_t tag, const char* name, int32_t value);
 } // namespace os
 
 class LIBBINDER_EXPORTED ScopedTrace {
diff --git a/libs/binder/liblog_stub/Android.bp b/libs/binder/liblog_stub/Android.bp
index f2ca22f..2de6658 100644
--- a/libs/binder/liblog_stub/Android.bp
+++ b/libs/binder/liblog_stub/Android.bp
@@ -30,6 +30,7 @@
     product_available: true,
     recovery_available: true,
     vendor_available: true,
+    cmake_snapshot_supported: true,
 
     target: {
         windows: {
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 9a2d14a..26c228d 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -32,17 +32,11 @@
     ],
 }
 
-cc_library {
-    name: "libbinder_ndk",
-
+cc_defaults {
+    name: "libbinder_ndk_common_defaults",
     host_supported: true,
     recovery_available: true,
 
-    llndk: {
-        symbol_file: "libbinder_ndk.map.txt",
-        export_llndk_headers: ["libvendorsupport_llndk_headers"],
-    },
-
     export_include_dirs: [
         "include_cpp",
         "include_ndk",
@@ -50,7 +44,6 @@
     ],
 
     cflags: [
-        "-DBINDER_WITH_KERNEL_IPC",
         "-Wall",
         "-Wextra",
         "-Wextra-semi",
@@ -59,14 +52,48 @@
 
     srcs: [
         "ibinder.cpp",
-        "ibinder_jni.cpp",
         "libbinder.cpp",
         "parcel.cpp",
+        "stability.cpp",
+        "status.cpp",
+    ],
+}
+
+cc_library_host_shared {
+    name: "libbinder_ndk_sdk",
+
+    defaults: [
+        "libbinder_ndk_common_defaults",
+        "binder_sdk_defaults",
+    ],
+    cmake_snapshot_supported: true,
+
+    shared_libs: [
+        "libbinder_sdk",
+        "libutils_binder_sdk",
+    ],
+}
+
+cc_library {
+    name: "libbinder_ndk",
+
+    defaults: ["libbinder_ndk_common_defaults"],
+    cmake_snapshot_supported: false,
+
+    llndk: {
+        symbol_file: "libbinder_ndk.map.txt",
+        export_llndk_headers: ["libvendorsupport_llndk_headers"],
+    },
+
+    cflags: [
+        "-DBINDER_WITH_KERNEL_IPC",
+    ],
+
+    srcs: [
+        "ibinder_jni.cpp",
         "parcel_jni.cpp",
         "persistable_bundle.cpp",
         "process.cpp",
-        "stability.cpp",
-        "status.cpp",
         "service_manager.cpp",
     ],
 
@@ -195,6 +222,7 @@
     host_supported: true,
     // TODO(b/153609531): remove when no longer needed.
     native_bridge_supported: true,
+    cmake_snapshot_supported: true,
     target: {
         darwin: {
             enabled: false,
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index d570eab..cf7dc1a 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -26,13 +26,10 @@
 #if defined(__ANDROID_VENDOR__)
 #include <android/llndk-versioning.h>
 #else  // __ANDROID_VENDOR__
-#if defined(API_LEVEL_AT_LEAST)
-// Redefine API_LEVEL_AT_LEAST here to replace the version to __ANDROID_API_FUTURE__ as a workaround
-#undef API_LEVEL_AT_LEAST
-#endif
-// TODO(b/322384429) switch this __ANDROID_API_FUTURE__ to sdk_api_level when V is finalized
+#if !defined(API_LEVEL_AT_LEAST)
 #define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
-    (__builtin_available(android __ANDROID_API_FUTURE__, *))
+    (__builtin_available(android sdk_api_level, *))
+#endif
 #endif  // __ANDROID_VENDOR__
 
 namespace aidl::android::os {
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 52edae4..41b30a0 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -30,7 +30,11 @@
      * Services with methods that perform file IO, web socket creation or ways to egress data must
      * not be added with this flag for privacy concerns.
      */
-    ADD_SERVICE_ALLOW_ISOLATED = 1,
+    ADD_SERVICE_ALLOW_ISOLATED = 1 << 0,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL = 1 << 1,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH = 1 << 2,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL = 1 << 3,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT = 1 << 4,
 };
 
 /**
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 5529455..4436dbe 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -49,7 +49,25 @@
     sp<IServiceManager> sm = defaultServiceManager();
 
     bool allowIsolated = flags & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
-    status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
+    int dumpFlags = 0;
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL;
+    }
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_HIGH;
+    }
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_NORMAL;
+    }
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT;
+    }
+    if (dumpFlags == 0) {
+        dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT;
+    }
+    status_t exception =
+            sm->addService(String16(instance), binder->getBinder(), allowIsolated, dumpFlags);
+
     return PruneException(exception);
 }
 
diff --git a/libs/binder/rust/tests/Android.bp b/libs/binder/rust/tests/Android.bp
index 2d1175b..f5b0071 100644
--- a/libs/binder/rust/tests/Android.bp
+++ b/libs/binder/rust/tests/Android.bp
@@ -114,7 +114,6 @@
     crate_name: "binder_rs_serialization_bindgen",
     wrapper_src: "serialization.hpp",
     source_stem: "bindings",
-    cpp_std: "gnu++17",
     bindgen_flags: [
         "--allowlist-type", "Transaction",
         "--allowlist-var", "TESTDATA_.*",
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index bd24a20..4c7684c 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -25,6 +25,7 @@
 
 cc_defaults {
     name: "binder_test_defaults",
+    cmake_snapshot_supported: true,
     cflags: [
         "-Wall",
         "-Werror",
@@ -62,6 +63,7 @@
         "binderStatusUnitTest.cpp",
         "binderMemoryHeapBaseUnitTest.cpp",
         "binderRecordedTransactionTest.cpp",
+        "binderPersistableBundleTest.cpp",
     ],
     shared_libs: [
         "libbinder",
@@ -141,6 +143,7 @@
     name: "binderRpcTestIface",
     vendor_available: true,
     host_supported: true,
+    cmake_snapshot_supported: true,
     unstable: true,
     srcs: [
         "BinderRpcTestClientInfo.aidl",
@@ -222,6 +225,7 @@
 cc_defaults {
     name: "binderRpcTest_common_defaults",
     host_supported: true,
+    cmake_snapshot_supported: true,
     target: {
         darwin: {
             enabled: false,
@@ -263,6 +267,7 @@
     defaults: [
         "binderRpcTest_common_defaults",
     ],
+    compile_multilib: "first",
 
     srcs: [
         "binderRpcTest.cpp",
@@ -332,7 +337,7 @@
     ],
 }
 
-cc_test {
+cc_binary {
     // The module name cannot start with "binderRpcTest" because
     // then atest tries to execute it as part of binderRpcTest
     name: "binder_rpc_test_service",
@@ -343,7 +348,7 @@
     ],
 }
 
-cc_test {
+cc_binary {
     name: "binder_rpc_test_service_no_kernel",
     defaults: [
         "binderRpcTest_service_defaults",
@@ -354,7 +359,7 @@
     ],
 }
 
-cc_test {
+cc_binary {
     name: "binder_rpc_test_service_single_threaded",
     defaults: [
         "binderRpcTest_service_defaults",
@@ -369,7 +374,7 @@
     ],
 }
 
-cc_test {
+cc_binary {
     name: "binder_rpc_test_service_single_threaded_no_kernel",
     defaults: [
         "binderRpcTest_service_defaults",
@@ -381,6 +386,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded_no_kernel",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_binary {
@@ -461,6 +469,20 @@
 }
 
 cc_test {
+    name: "binderRpcTestNoKernelAtAll",
+    defaults: [
+        "binderRpcTest_defaults",
+        "binderRpcTest_static_defaults",
+    ],
+    static_libs: [
+        "libbinder_rpc_no_kernel",
+    ],
+    cflags: [
+        "-DBINDER_NO_KERNEL_IPC_TESTING",
+    ],
+}
+
+cc_test {
     name: "binderRpcTestSingleThreaded",
     defaults: [
         "binderRpcTest_defaults",
@@ -487,6 +509,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded_no_kernel",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_test {
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 9788d9d..00406ed 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -38,6 +38,7 @@
 #include <binder/IServiceManager.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
+#include <binder/Status.h>
 #include <binder/unique_fd.h>
 #include <utils/Flattenable.h>
 
@@ -57,6 +58,7 @@
 using namespace std::chrono_literals;
 using android::base::testing::HasValue;
 using android::base::testing::Ok;
+using android::binder::Status;
 using android::binder::unique_fd;
 using testing::ExplainMatchResult;
 using testing::Matcher;
@@ -253,7 +255,7 @@
     public:
         virtual void SetUp() {
             m_server = static_cast<BinderLibTestEnv *>(binder_env)->getServer();
-            IPCThreadState::self()->restoreCallingWorkSource(0); 
+            IPCThreadState::self()->restoreCallingWorkSource(0);
         }
         virtual void TearDown() {
         }
@@ -461,6 +463,35 @@
     EXPECT_EQ(NO_ERROR, sm->addService(String16("binderLibTest-manager"), binder));
 }
 
+TEST_F(BinderLibTest, RegisterForNotificationsFailure) {
+    auto sm = defaultServiceManager();
+    using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
+    class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
+        void onServiceRegistration(const String16&, const sp<IBinder>&) override {}
+        virtual ~LocalRegistrationCallbackImpl() {}
+    };
+    sp<LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
+
+    EXPECT_EQ(BAD_VALUE, sm->registerForNotifications(String16("ValidName"), nullptr));
+    EXPECT_EQ(UNKNOWN_ERROR, sm->registerForNotifications(String16("InvalidName!$"), cb));
+}
+
+TEST_F(BinderLibTest, UnregisterForNotificationsFailure) {
+    auto sm = defaultServiceManager();
+    using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
+    class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
+        void onServiceRegistration(const String16&, const sp<IBinder>&) override {}
+        virtual ~LocalRegistrationCallbackImpl() {}
+    };
+    sp<LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
+
+    EXPECT_EQ(OK, sm->registerForNotifications(String16("ValidName"), cb));
+
+    EXPECT_EQ(BAD_VALUE, sm->unregisterForNotifications(String16("ValidName"), nullptr));
+    EXPECT_EQ(BAD_VALUE, sm->unregisterForNotifications(String16("AnotherValidName"), cb));
+    EXPECT_EQ(BAD_VALUE, sm->unregisterForNotifications(String16("InvalidName!!!"), cb));
+}
+
 TEST_F(BinderLibTest, WasParceled) {
     auto binder = sp<BBinder>::make();
     EXPECT_FALSE(binder->wasParceled());
diff --git a/libs/binder/tests/binderPersistableBundleTest.cpp b/libs/binder/tests/binderPersistableBundleTest.cpp
new file mode 100644
index 0000000..392e018
--- /dev/null
+++ b/libs/binder/tests/binderPersistableBundleTest.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Parcel.h>
+#include <binder/PersistableBundle.h>
+#include <gtest/gtest.h>
+#include <numeric>
+
+using android::OK;
+using android::Parcel;
+using android::status_t;
+using android::String16;
+using android::String8;
+using android::os::PersistableBundle;
+
+namespace android {
+
+inline std::string to_string(String16 const& str) {
+    return String8{str}.c_str();
+}
+
+namespace os {
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& out, std::vector<T> const& vec) {
+    using std::to_string;
+    auto str =
+            std::accumulate(vec.begin(), vec.end(), std::string{},
+                            [](std::string const& a, auto const& b) { return a + to_string(b); });
+    return out << str;
+}
+
+inline std::ostream& operator<<(std::ostream& out, PersistableBundle const& pb) {
+#define PRINT(TYPENAME, TYPE)                                \
+    for (auto const& key : pb.get##TYPENAME##Keys()) {       \
+        TYPE val{};                                          \
+        pb.get##TYPENAME(key, &val);                         \
+        out << #TYPE " " << key << ": " << val << std::endl; \
+    }
+
+    out << "size: " << pb.size() << std::endl;
+    PRINT(Boolean, bool);
+    PRINT(Int, int);
+    PRINT(Long, int64_t);
+    PRINT(Double, double);
+    PRINT(String, String16);
+    PRINT(BooleanVector, std::vector<bool>);
+    PRINT(IntVector, std::vector<int32_t>);
+    PRINT(LongVector, std::vector<int64_t>);
+    PRINT(DoubleVector, std::vector<double>);
+    PRINT(StringVector, std::vector<String16>);
+    PRINT(PersistableBundle, PersistableBundle);
+
+#undef PRINT
+
+    return out;
+}
+
+} // namespace os
+} // namespace android
+
+static const String16 kKey{"key"};
+
+static PersistableBundle createSimplePersistableBundle() {
+    PersistableBundle pb{};
+    pb.putInt(kKey, 64);
+    return pb;
+}
+
+#define TEST_PUT_AND_GET(TYPENAME, TYPE, ...)              \
+    TEST(PersistableBundle, PutAndGet##TYPENAME) {         \
+        TYPE const expected{__VA_ARGS__};                  \
+        PersistableBundle pb{};                            \
+                                                           \
+        pb.put##TYPENAME(kKey, expected);                  \
+                                                           \
+        std::set<String16> expectedKeys{kKey};             \
+        EXPECT_EQ(pb.get##TYPENAME##Keys(), expectedKeys); \
+                                                           \
+        TYPE val{};                                        \
+        EXPECT_TRUE(pb.get##TYPENAME(kKey, &val));         \
+        EXPECT_EQ(val, expected);                          \
+    }
+
+TEST_PUT_AND_GET(Boolean, bool, true);
+TEST_PUT_AND_GET(Int, int, 64);
+TEST_PUT_AND_GET(Long, int64_t, 42);
+TEST_PUT_AND_GET(Double, double, 42.64);
+TEST_PUT_AND_GET(String, String16, String16{"foo"});
+TEST_PUT_AND_GET(BooleanVector, std::vector<bool>, true, true);
+TEST_PUT_AND_GET(IntVector, std::vector<int32_t>, 1, 2);
+TEST_PUT_AND_GET(LongVector, std::vector<int64_t>, 1, 2);
+TEST_PUT_AND_GET(DoubleVector, std::vector<double>, 4.2, 5.9);
+TEST_PUT_AND_GET(StringVector, std::vector<String16>, String16{"foo"}, String16{"bar"});
+TEST_PUT_AND_GET(PersistableBundle, PersistableBundle, createSimplePersistableBundle());
+
+TEST(PersistableBundle, ParcelAndUnparcel) {
+    PersistableBundle expected = createSimplePersistableBundle();
+    PersistableBundle out{};
+
+    Parcel p{};
+    EXPECT_EQ(expected.writeToParcel(&p), 0);
+    p.setDataPosition(0);
+    EXPECT_EQ(out.readFromParcel(&p), 0);
+
+    EXPECT_EQ(expected, out);
+}
+
+TEST(PersistableBundle, OverwriteKey) {
+    PersistableBundle pb{};
+
+    pb.putInt(kKey, 64);
+    pb.putDouble(kKey, 0.5);
+
+    EXPECT_EQ(pb.getIntKeys().size(), 0);
+    EXPECT_EQ(pb.getDoubleKeys().size(), 1);
+
+    double out;
+    EXPECT_TRUE(pb.getDouble(kKey, &out));
+    EXPECT_EQ(out, 0.5);
+}
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index c044d39..19882ea 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1278,7 +1278,7 @@
                 for (const auto& clientVersion : testVersions()) {
                     for (const auto& serverVersion : testVersions()) {
                         for (bool singleThreaded : {false, true}) {
-                            for (bool noKernel : {false, true}) {
+                            for (bool noKernel : noKernelValues()) {
                                 ret.push_back(BinderRpc::ParamType{
                                         .type = type,
                                         .security = security,
@@ -1299,7 +1299,7 @@
                     .clientVersion = RPC_WIRE_PROTOCOL_VERSION,
                     .serverVersion = RPC_WIRE_PROTOCOL_VERSION,
                     .singleThreaded = false,
-                    .noKernel = false,
+                    .noKernel = !kEnableKernelIpcTesting,
             });
         }
     }
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index acc0373..dc22647 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -64,6 +64,12 @@
 
 namespace android {
 
+#ifdef BINDER_NO_KERNEL_IPC_TESTING
+constexpr bool kEnableKernelIpcTesting = false;
+#else
+constexpr bool kEnableKernelIpcTesting = true;
+#endif
+
 constexpr char kLocalInetAddress[] = "127.0.0.1";
 
 enum class RpcSecurity { RAW, TLS };
@@ -72,6 +78,14 @@
     return {RpcSecurity::RAW, RpcSecurity::TLS};
 }
 
+static inline std::vector<bool> noKernelValues() {
+    std::vector<bool> values = {true};
+    if (kEnableKernelIpcTesting) {
+        values.push_back(false);
+    }
+    return values;
+}
+
 static inline bool hasExperimentalRpc() {
 #ifdef BINDER_RPC_TO_TRUSTY_TEST
     // Trusty services do not support the experimental version,
diff --git a/libs/binder/tests/binderRpcWireProtocolTest.cpp b/libs/binder/tests/binderRpcWireProtocolTest.cpp
index e59dc82..91145f0 100644
--- a/libs/binder/tests/binderRpcWireProtocolTest.cpp
+++ b/libs/binder/tests/binderRpcWireProtocolTest.cpp
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/strings.h>
 #include <binder/Parcel.h>
 #include <binder/RpcSession.h>
 #include <binder/Status.h>
 #include <gtest/gtest.h>
 
+#ifdef __ANDROID__
+#include <android-base/properties.h>
+#endif
+
 #include "../Debug.h"
 #include "../Utils.h"
 
@@ -69,8 +70,8 @@
     [](Parcel* p) { ASSERT_EQ(OK, p->writeString16(String16(u"a"))); },
     [](Parcel* p) { ASSERT_EQ(OK, p->writeString16(String16(u"baba"))); },
     [](Parcel* p) { ASSERT_EQ(OK, p->writeStrongBinder(nullptr)); },
-    [](Parcel* p) { ASSERT_EQ(OK, p->writeInt32Array(arraysize(kInt32Array), kInt32Array)); },
-    [](Parcel* p) { ASSERT_EQ(OK, p->writeByteArray(arraysize(kByteArray), kByteArray)); },
+    [](Parcel* p) { ASSERT_EQ(OK, p->writeInt32Array(countof(kInt32Array), kInt32Array)); },
+    [](Parcel* p) { ASSERT_EQ(OK, p->writeByteArray(countof(kByteArray), kByteArray)); },
     [](Parcel* p) { ASSERT_EQ(OK, p->writeBool(true)); },
     [](Parcel* p) { ASSERT_EQ(OK, p->writeBool(false)); },
     [](Parcel* p) { ASSERT_EQ(OK, p->writeChar('a')); },
@@ -162,8 +163,8 @@
 
 static void setParcelForRpc(Parcel* p, uint32_t version) {
     auto session = RpcSession::make();
-    CHECK(session->setProtocolVersion(version));
-    CHECK_EQ(OK, session->addNullDebuggingClient());
+    EXPECT_TRUE(session->setProtocolVersion(version));
+    EXPECT_EQ(OK, session->addNullDebuggingClient());
     p->markForRpc(session);
 }
 
@@ -180,13 +181,25 @@
     return result;
 }
 
+// To be replaced with std::views::split (and std::views::zip) once C++ compilers catch up.
+static std::vector<std::string> split(std::string_view s, char delimiter) {
+    std::vector<std::string> result;
+    size_t pos = 0;
+    while (true) {
+        const auto found = s.find(delimiter, pos);
+        result.emplace_back(s.substr(pos, found - pos));
+        if (found == s.npos) return result;
+        pos = found + 1;
+    }
+}
+
 static void checkRepr(const std::string& repr, uint32_t version) {
     const std::string actualRepr = buildRepr(version);
 
-    auto expected = base::Split(repr, "|");
+    auto expected = split(repr, '|');
     ASSERT_EQ(expected.size(), kFillFuns.size());
 
-    auto actual = base::Split(actualRepr, "|");
+    auto actual = split(actualRepr, '|');
     ASSERT_EQ(actual.size(), kFillFuns.size());
 
     for (size_t i = 0; i < kFillFuns.size(); i++) {
@@ -257,8 +270,13 @@
 
 TEST(RpcWire, ReleaseBranchHasFrozenRpcWireProtocol) {
     if (RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
-        EXPECT_FALSE(base::GetProperty("ro.build.version.codename", "") == "REL")
-                << "Binder RPC wire protocol must be frozen on a release branch!";
+#ifdef __ANDROID__
+        bool isRelease = base::GetProperty("ro.build.version.codename", "") == "REL";
+#else
+        bool isRelease = true;
+#endif
+        EXPECT_FALSE(isRelease)
+                << "Binder RPC wire protocol must be frozen in release configuration!";
     }
 }
 
diff --git a/libs/binder/tests/binder_sdk/Android.bp b/libs/binder/tests/binder_sdk/Android.bp
new file mode 100644
index 0000000..4e884ad
--- /dev/null
+++ b/libs/binder/tests/binder_sdk/Android.bp
@@ -0,0 +1,84 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // 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"],
+}
+
+sh_test_host {
+    name: "binder_sdk_test",
+    src: "binder_sdk_test.sh",
+    test_suites: ["general-tests"],
+    test_options: {
+        unit_test: false,
+    },
+
+    data: [
+        ":binder_sdk",
+        ":cmake_root",
+    ],
+    data_bins: [
+        "cmake",
+        "ctest",
+    ],
+}
+
+sh_test_host {
+    name: "binder_sdk_docker_test_gcc",
+    src: "binder_sdk_docker_test.sh",
+    test_suites: ["general-tests"],
+    test_options: {
+        unit_test: false,
+    },
+
+    data: [
+        ":binder_sdk",
+        "gcc.Dockerfile",
+    ],
+}
+
+sh_test_host {
+    name: "binder_sdk_docker_test_clang",
+    src: "binder_sdk_docker_test.sh",
+    test_suites: ["general-tests"],
+    test_options: {
+        unit_test: false,
+    },
+
+    data: [
+        ":binder_sdk",
+        "clang.Dockerfile",
+    ],
+}
+
+sh_test_host {
+    name: "binder_sdk_docker_test_gnumake",
+    src: "binder_sdk_docker_test.sh",
+    test_suites: ["general-tests"],
+    test_options: {
+        unit_test: false,
+    },
+
+    data: [
+        ":binder_sdk",
+        "gnumake.Dockerfile",
+    ],
+}
diff --git a/libs/binder/tests/binder_sdk/binder_sdk_docker_test.sh b/libs/binder/tests/binder_sdk/binder_sdk_docker_test.sh
new file mode 100755
index 0000000..0eca846
--- /dev/null
+++ b/libs/binder/tests/binder_sdk/binder_sdk_docker_test.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -ex
+
+TEST_NAME="$(basename "$0")"
+DOCKER_TAG="${TEST_NAME}-${RANDOM}${RANDOM}"
+DOCKER_FILE=*.Dockerfile
+DOCKER_RUN_FLAGS=
+
+# Guess if we're running as an Android test or directly
+if [ "$(ls -1 ${DOCKER_FILE} | wc -l)" == "1" ]; then
+    # likely running as `atest binder_sdk_docker_test_XYZ`
+    DOCKER_PATH="$(dirname $(readlink --canonicalize --no-newline binder_sdk.zip))"
+else
+    # likely running directly as `./binder_sdk_docker_test.sh` - provide mode for easy testing
+    RED='\033[1;31m'
+    NO_COLOR='\033[0m'
+
+    if ! modinfo vsock_loopback &>/dev/null ; then
+        echo -e "${RED}Module vsock_loopback is not installed.${NO_COLOR}"
+        exit 1
+    fi
+    if modprobe --dry-run --first-time vsock_loopback &>/dev/null ; then
+        echo "Module vsock_loopback is not loaded. Attempting to load..."
+        if ! sudo modprobe vsock_loopback ; then
+            echo -e "${RED}Module vsock_loopback is not loaded and attempt to load failed.${NO_COLOR}"
+            exit 1
+        fi
+    fi
+
+    DOCKER_RUN_FLAGS="--interactive --tty"
+
+    DOCKER_FILE="$1"
+    if [ ! -f "${DOCKER_FILE}" ]; then
+        echo -e "${RED}Docker file '${DOCKER_FILE}' doesn't exist. Please provide one as an argument.${NO_COLOR}"
+        exit 1
+    fi
+
+    if [ ! -d "${ANDROID_BUILD_TOP}" ]; then
+        echo -e "${RED}ANDROID_BUILD_TOP doesn't exist. Please lunch some target.${NO_COLOR}"
+        exit 1
+    fi
+    ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode binder_sdk
+    BINDER_SDK_ZIP="${ANDROID_BUILD_TOP}/out/soong/.intermediates/frameworks/native/libs/binder/binder_sdk/linux_glibc_x86_64/*/binder_sdk.zip"
+    DOCKER_PATH="$(dirname $(ls -1 ${BINDER_SDK_ZIP} | head --lines=1))"
+fi
+
+function cleanup {
+    docker rmi --force "${DOCKER_TAG}" 2>/dev/null || true
+}
+trap cleanup EXIT
+
+docker build --force-rm --tag "${DOCKER_TAG}" --file ${DOCKER_FILE} ${DOCKER_PATH}
+docker run ${DOCKER_RUN_FLAGS} --rm "${DOCKER_TAG}"
diff --git a/libs/binder/tests/binder_sdk/binder_sdk_test.sh b/libs/binder/tests/binder_sdk/binder_sdk_test.sh
new file mode 100644
index 0000000..1881ace
--- /dev/null
+++ b/libs/binder/tests/binder_sdk/binder_sdk_test.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -ex
+
+RED='\033[1;31m'
+NO_COLOR='\033[0m'
+
+if [ ! -f "binder_sdk.zip" ]; then
+    echo -e "${RED}binder_sdk.zip doesn't exist. Are you running this test through 'atest binder_sdk_test'?${NO_COLOR}"
+    exit 1
+fi
+
+mkdir -p bin
+cp `pwd`/cmake bin/cmake
+cp `pwd`/ctest bin/ctest
+export PATH="`pwd`/bin:$PATH"
+
+WORKDIR=workdir_$RANDOM$RANDOM$RANDOM
+unzip -q -d $WORKDIR binder_sdk.zip
+cd $WORKDIR
+
+cmake .
+make -j
+make test ARGS="--parallel 32 --output-on-failure"
diff --git a/libs/binder/tests/binder_sdk/clang.Dockerfile b/libs/binder/tests/binder_sdk/clang.Dockerfile
new file mode 100644
index 0000000..aa1fec2
--- /dev/null
+++ b/libs/binder/tests/binder_sdk/clang.Dockerfile
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM debian:bookworm
+
+RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list && \
+    apt-get update -y && \
+    apt-get install -y clang cmake ninja-build unzip
+
+ADD binder_sdk.zip /
+RUN unzip -q -d binder_sdk binder_sdk.zip
+
+WORKDIR /binder_sdk
+RUN CC=clang CXX=clang++ cmake -G Ninja -B build .
+RUN cmake --build build
+
+WORKDIR /binder_sdk/build
+# Alternatively: `ninja test`, but it won't pass parallel argument
+ENTRYPOINT [ "ctest", "--parallel", "32", "--output-on-failure" ]
diff --git a/libs/binder/tests/binder_sdk/gcc.Dockerfile b/libs/binder/tests/binder_sdk/gcc.Dockerfile
new file mode 100644
index 0000000..fb2ee2c
--- /dev/null
+++ b/libs/binder/tests/binder_sdk/gcc.Dockerfile
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM gcc:9
+
+RUN echo 'deb http://deb.debian.org/debian bullseye-backports main' >> /etc/apt/sources.list && \
+    apt-get update -y && \
+    apt-get install -y cmake ninja-build
+
+ADD binder_sdk.zip /
+RUN unzip -q -d binder_sdk binder_sdk.zip
+
+WORKDIR /binder_sdk
+RUN CC=gcc CXX=g++ cmake -G Ninja -B build .
+RUN cmake --build build
+
+WORKDIR /binder_sdk/build
+# Alternatively: `ninja test`, but it won't pass parallel argument
+ENTRYPOINT [ "ctest", "--parallel", "32", "--output-on-failure" ]
diff --git a/libs/binder/tests/binder_sdk/gnumake.Dockerfile b/libs/binder/tests/binder_sdk/gnumake.Dockerfile
new file mode 100644
index 0000000..abe12fb
--- /dev/null
+++ b/libs/binder/tests/binder_sdk/gnumake.Dockerfile
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM gcc:9
+
+RUN echo 'deb http://deb.debian.org/debian bullseye-backports main' >> /etc/apt/sources.list && \
+    apt-get update -y && \
+    apt-get install -y cmake
+
+ADD binder_sdk.zip /
+RUN unzip -q -d binder_sdk binder_sdk.zip
+
+WORKDIR /binder_sdk
+RUN cmake .
+RUN make -j
+
+ENTRYPOINT make test ARGS="--parallel 32 --output-on-failure"
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 09db326..157ab3c 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -40,6 +40,8 @@
 
 void trace_end(uint64_t) {}
 
+void trace_int(uint64_t, const char*, int32_t) {}
+
 uint64_t GetThreadId() {
     return 0;
 }
diff --git a/libs/binder/trusty/binderRpcTest/rules.mk b/libs/binder/trusty/binderRpcTest/rules.mk
index e46ccfb..975f689 100644
--- a/libs/binder/trusty/binderRpcTest/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/rules.mk
@@ -21,7 +21,6 @@
 MANIFEST := $(LOCAL_DIR)/manifest.json
 
 MODULE_SRCS += \
-	$(FMTLIB_DIR)/src/format.cc \
 	$(LIBBINDER_TESTS_DIR)/binderRpcUniversalTests.cpp \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestTrusty.cpp \
diff --git a/libs/binder/trusty/binderRpcTest/service/rules.mk b/libs/binder/trusty/binderRpcTest/service/rules.mk
index 50ae3d2..5d1a51d 100644
--- a/libs/binder/trusty/binderRpcTest/service/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/service/rules.mk
@@ -21,7 +21,6 @@
 MANIFEST := $(LOCAL_DIR)/manifest.json
 
 MODULE_SRCS := \
-	$(FMTLIB_DIR)/src/format.cc \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestServiceTrusty.cpp \
 
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index 5cbe0af..7caa48c 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -22,7 +22,6 @@
 LIBBASE_DIR := system/libbase
 LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
 LIBUTILS_BINDER_DIR := system/core/libutils/binder
-FMTLIB_DIR := external/fmtlib
 
 MODULE_SRCS := \
 	$(LOCAL_DIR)/../OS.cpp \
@@ -59,7 +58,6 @@
 	$(LIBBINDER_DIR)/ndk/include_cpp \
 	$(LIBBASE_DIR)/include \
 	$(LIBUTILS_BINDER_DIR)/include \
-	$(FMTLIB_DIR)/include \
 
 GLOBAL_COMPILEFLAGS += \
 	-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION \
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index f2f140d..5e38ad0 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -22,7 +22,6 @@
 LIBBASE_DIR := system/libbase
 LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
 LIBUTILS_BINDER_DIR := system/core/libutils/binder
-FMTLIB_DIR := external/fmtlib
 
 MODULE_SRCS := \
 	$(LOCAL_DIR)/OS.cpp \
@@ -59,7 +58,6 @@
 	$(LIBBINDER_DIR)/include \
 	$(LIBBASE_DIR)/include \
 	$(LIBUTILS_BINDER_DIR)/include \
-	$(FMTLIB_DIR)/include \
 
 # The android/binder_to_string.h header is shared between libbinder and
 # libbinder_ndk and included by auto-generated AIDL C++ files
diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp
index 9b7f017..d5b1d7e 100644
--- a/libs/ftl/expected_test.cpp
+++ b/libs/ftl/expected_test.cpp
@@ -79,16 +79,28 @@
 
 namespace {
 
-IntExp increment(IntExp exp) {
+IntExp increment_try(IntExp exp) {
   const int i = FTL_TRY(exp);
   return IntExp(i + 1);
 }
 
-StringExp repeat(StringExp exp) {
+std::errc increment_expect(IntExp exp, int& out) {
+  const int i = FTL_EXPECT(exp);
+  out = i + 1;
+  return std::errc::operation_in_progress;
+}
+
+StringExp repeat_try(StringExp exp) {
   const std::string str = FTL_TRY(exp);
   return StringExp(str + str);
 }
 
+std::errc repeat_expect(StringExp exp, std::string& out) {
+  const std::string str = FTL_EXPECT(exp);
+  out = str + str;
+  return std::errc::operation_in_progress;
+}
+
 void uppercase(char& c, ftl::Optional<char> opt) {
   c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
 }
@@ -97,13 +109,13 @@
 
 // Keep in sync with example usage in header file.
 TEST(Expected, Try) {
-  EXPECT_EQ(IntExp(100), increment(IntExp(99)));
-  EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) {
+  EXPECT_EQ(IntExp(100), increment_try(IntExp(99)));
+  EXPECT_TRUE(increment_try(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) {
     return e == std::errc::value_too_large;
   }));
 
-  EXPECT_EQ(StringExp("haha"s), repeat(StringExp("ha"s)));
-  EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
+  EXPECT_EQ(StringExp("haha"s), repeat_try(StringExp("ha"s)));
+  EXPECT_TRUE(repeat_try(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
     return e == std::errc::bad_message;
   }));
 
@@ -115,4 +127,19 @@
   EXPECT_EQ(c, 'A');
 }
 
+TEST(Expected, Expect) {
+  int i = 0;
+  EXPECT_EQ(std::errc::operation_in_progress, increment_expect(IntExp(99), i));
+  EXPECT_EQ(100, i);
+  EXPECT_EQ(std::errc::value_too_large,
+            increment_expect(ftl::Unexpected(std::errc::value_too_large), i));
+  EXPECT_EQ(100, i);
+
+  std::string str;
+  EXPECT_EQ(std::errc::operation_in_progress, repeat_expect(StringExp("ha"s), str));
+  EXPECT_EQ("haha"s, str);
+  EXPECT_EQ(std::errc::bad_message, repeat_expect(ftl::Unexpected(std::errc::bad_message), str));
+  EXPECT_EQ("haha"s, str);
+}
+
 }  // namespace android::test
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 2547297..9ef0eac 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -41,6 +41,11 @@
     aconfig_declarations: "libgui_flags",
 }
 
+cc_aconfig_library {
+    name: "libguiflags_no_apex",
+    aconfig_declarations: "libgui_flags",
+}
+
 cc_library_headers {
     name: "libgui_headers",
     vendor_available: true,
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index f317a2e..739c3c2 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -613,8 +613,19 @@
             std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
                       std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
     sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
+
+    nsecs_t dequeueTime = -1;
+    {
+        std::lock_guard _lock{mTimestampMutex};
+        auto dequeueTimeIt = mDequeueTimestamps.find(buffer->getId());
+        if (dequeueTimeIt != mDequeueTimestamps.end()) {
+            dequeueTime = dequeueTimeIt->second;
+            mDequeueTimestamps.erase(dequeueTimeIt);
+        }
+    }
+
     t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId,
-                 releaseBufferCallback);
+                 releaseBufferCallback, dequeueTime);
     t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
     t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
     t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
@@ -658,17 +669,6 @@
         mPendingFrameTimelines.pop();
     }
 
-    {
-        std::lock_guard _lock{mTimestampMutex};
-        auto dequeueTime = mDequeueTimestamps.find(buffer->getId());
-        if (dequeueTime != mDequeueTimestamps.end()) {
-            Parcel p;
-            p.writeInt64(dequeueTime->second);
-            t->setMetadata(mSurfaceControl, gui::METADATA_DEQUEUE_TIME, p);
-            mDequeueTimestamps.erase(dequeueTime);
-        }
-    }
-
     mergePendingTransactions(t, bufferItem.mFrameNumber);
     if (applyTransaction) {
         // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index f3de96d..c46f9c5 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "DisplayEventDispatcher"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <cinttypes>
 #include <cstdint>
@@ -23,10 +24,13 @@
 #include <gui/DisplayEventReceiver.h>
 #include <utils/Log.h>
 #include <utils/Looper.h>
-
 #include <utils/Timers.h>
+#include <utils/Trace.h>
+
+#include <com_android_graphics_libgui_flags.h>
 
 namespace android {
+using namespace com::android::graphics::libgui;
 
 // Number of events to read at a time from the DisplayEventDispatcher pipe.
 // The value should be large enough that we can quickly drain the pipe
@@ -171,6 +175,13 @@
                     *outDisplayId = ev.header.displayId;
                     *outCount = ev.vsync.count;
                     *outVsyncEventData = ev.vsync.vsyncData;
+
+                    // Trace the RenderRate for this app
+                    if (ATRACE_ENABLED() && flags::trace_frame_rate_override()) {
+                        const auto frameInterval = ev.vsync.vsyncData.frameInterval;
+                        int fps = frameInterval > 0 ? 1e9f / frameInterval : 0;
+                        ATRACE_INT("RenderRate", fps);
+                    }
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
                     if (ev.hotplug.connectionError == 0) {
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index ff6b558..2699368 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -62,7 +62,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
             const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
             const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index c82bde9..3745805 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -985,6 +985,7 @@
     SAFE_PARCEL(output->writeBool, hasBarrier);
     SAFE_PARCEL(output->writeUint64, barrierFrameNumber);
     SAFE_PARCEL(output->writeUint32, producerId);
+    SAFE_PARCEL(output->writeInt64, dequeueTime);
 
     return NO_ERROR;
 }
@@ -1024,6 +1025,7 @@
     SAFE_PARCEL(input->readBool, &hasBarrier);
     SAFE_PARCEL(input->readUint64, &barrierFrameNumber);
     SAFE_PARCEL(input->readUint32, &producerId);
+    SAFE_PARCEL(input->readInt64, &dequeueTime);
 
     return NO_ERROR;
 }
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index b420aab..5db5394 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1059,7 +1059,8 @@
     uncacheBuffer.token = BufferCache::getInstance().getToken();
     uncacheBuffer.id = cacheId;
     Vector<ComposerState> composerStates;
-    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+    Vector<DisplayState> displayStates;
+    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, displayStates,
                                               ISurfaceComposer::eOneWay,
                                               Transaction::getDefaultApplyToken(), {}, systemTime(),
                                               true, {uncacheBuffer}, false, {}, generateId(), {});
@@ -1702,7 +1703,7 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
         const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
         const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& optFrameNumber,
-        uint32_t producerId, ReleaseBufferCallback callback) {
+        uint32_t producerId, ReleaseBufferCallback callback, nsecs_t dequeueTime) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1718,6 +1719,7 @@
         bufferData->frameNumber = frameNumber;
         bufferData->producerId = producerId;
         bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
+        bufferData->dequeueTime = dequeueTime;
         if (fence) {
             bufferData->acquireFence = *fence;
             bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index eb4a802..1ecc216 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -112,7 +112,7 @@
     /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
     virtual status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index 9cf62bc..7ee291d 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -74,6 +74,7 @@
 } // namespace android::gui
 
 using android::gui::METADATA_ACCESSIBILITY_ID;
+using android::gui::METADATA_CALLING_UID;
 using android::gui::METADATA_DEQUEUE_TIME;
 using android::gui::METADATA_GAME_MODE;
 using android::gui::METADATA_MOUSE_CURSOR;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 82889ef..5f2f8dc 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -128,6 +128,8 @@
 
     client_cache_t cachedBuffer;
 
+    nsecs_t dequeueTime;
+
     // Generates the release callback id based on the buffer id and frame number.
     // This is used as an identifier when release callbacks are invoked.
     ReleaseCallbackId generateReleaseCallbackId() const;
@@ -276,9 +278,9 @@
             layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
 
     // Changes affecting data sent to input.
-    static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged |
-            layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged |
-            layer_state_t::eLayerStackChanged;
+    static constexpr uint64_t INPUT_CHANGES = layer_state_t::eAlphaChanged |
+            layer_state_t::eInputInfoChanged | layer_state_t::eDropInputModeChanged |
+            layer_state_t::eTrustedOverlayChanged | layer_state_t::eLayerStackChanged;
 
     // Changes that affect the visible region on a display.
     static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 9712907..0862e03 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -568,7 +568,8 @@
         Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
                                const std::optional<sp<Fence>>& fence = std::nullopt,
                                const std::optional<uint64_t>& frameNumber = std::nullopt,
-                               uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr);
+                               uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr,
+                               nsecs_t dequeueTime = -1);
         Transaction& unsetBuffer(const sp<SurfaceControl>& sc);
         std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
 
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index a902a8c..792966f 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -7,7 +7,7 @@
   description: "This flag controls plumbing setFrameRate thru BufferQueue"
   bug: "281695725"
   is_fixed_read_only: true
-}
+} # bq_setframerate
 
 flag {
   name: "frametimestamps_previousrelease"
@@ -15,7 +15,7 @@
   description: "Controls a fence fixup for timestamp apis"
   bug: "310927247"
   is_fixed_read_only: true
-}
+} # frametimestamps_previousrelease
 
 flag {
   name: "bq_extendedallocate"
@@ -23,4 +23,15 @@
   description: "Add BQ support for allocate with extended options"
   bug: "268382490"
   is_fixed_read_only: true
-}
+} # bq_extendedallocate
+
+flag {
+  name: "trace_frame_rate_override"
+  namespace: "core_graphics"
+  description: "Trace FrameRateOverride fps"
+  bug: "347314033"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # trace_frame_rate_override
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 43cd0f8..5e91088 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -636,7 +636,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
-            const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+            Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
             const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
             int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
             const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index ed17014..c2a7ebb 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
         "android/os/InputConfig.aidl",
+        "android/os/MotionEventFlag.aidl",
         "android/os/PointerIconType.aidl",
     ],
 }
@@ -113,6 +114,27 @@
         "--allowlist-var=AINPUT_SOURCE_HDMI",
         "--allowlist-var=AINPUT_SOURCE_SENSOR",
         "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
+        "--allowlist-var=AINPUT_KEYBOARD_TYPE_NONE",
+        "--allowlist-var=AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC",
+        "--allowlist-var=AINPUT_KEYBOARD_TYPE_ALPHABETIC",
+        "--allowlist-var=AMETA_NONE",
+        "--allowlist-var=AMETA_ALT_ON",
+        "--allowlist-var=AMETA_ALT_LEFT_ON",
+        "--allowlist-var=AMETA_ALT_RIGHT_ON",
+        "--allowlist-var=AMETA_SHIFT_ON",
+        "--allowlist-var=AMETA_SHIFT_LEFT_ON",
+        "--allowlist-var=AMETA_SHIFT_RIGHT_ON",
+        "--allowlist-var=AMETA_SYM_ON",
+        "--allowlist-var=AMETA_FUNCTION_ON",
+        "--allowlist-var=AMETA_CTRL_ON",
+        "--allowlist-var=AMETA_CTRL_LEFT_ON",
+        "--allowlist-var=AMETA_CTRL_RIGHT_ON",
+        "--allowlist-var=AMETA_META_ON",
+        "--allowlist-var=AMETA_META_LEFT_ON",
+        "--allowlist-var=AMETA_META_RIGHT_ON",
+        "--allowlist-var=AMETA_CAPS_LOCK_ON",
+        "--allowlist-var=AMETA_NUM_LOCK_ON",
+        "--allowlist-var=AMETA_SCROLL_LOCK_ON",
     ],
 
     static_libs: [
@@ -204,6 +226,7 @@
         "InputVerifier.cpp",
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
+        "KeyboardClassifier.cpp",
         "KeyLayoutMap.cpp",
         "MotionPredictor.cpp",
         "MotionPredictorMetricsManager.cpp",
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index ee121d5..b098147 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -96,6 +96,19 @@
     return AMOTION_EVENT_ACTION_DOWN;
 }
 
+float transformOrientation(const ui::Transform& transform, const PointerCoords& coords,
+                           int32_t motionEventFlags) {
+    if ((motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION) == 0) {
+        return 0;
+    }
+
+    const bool isDirectionalAngle =
+            (motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION) != 0;
+
+    return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
+                          isDirectionalAngle);
+}
+
 } // namespace
 
 const char* motionClassificationToString(MotionClassification classification) {
@@ -187,7 +200,7 @@
     return roundTransformedCoords(transformedXy - transformedOrigin);
 }
 
-float transformAngle(const ui::Transform& transform, float angleRadians) {
+float transformAngle(const ui::Transform& transform, float angleRadians, bool isDirectional) {
     // Construct and transform a vector oriented at the specified clockwise angle from vertical.
     // Coordinate system: down is increasing Y, right is increasing X.
     float x = sinf(angleRadians);
@@ -201,6 +214,11 @@
     transformedPoint.x -= origin.x;
     transformedPoint.y -= origin.y;
 
+    if (!isDirectional && transformedPoint.y > 0) {
+        // Limit the range of atan2f to [-pi/2, pi/2] by reversing the direction of the vector.
+        transformedPoint *= -1;
+    }
+
     // Derive the transformed vector's clockwise angle from vertical.
     // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API.
     return atan2f(transformedPoint.x, -transformedPoint.y);
@@ -530,26 +548,6 @@
     return true;
 }
 
-void PointerCoords::transform(const ui::Transform& transform) {
-    const vec2 xy = transform.transform(getXYValue());
-    setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
-    setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
-
-    if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_RELATIVE_X) ||
-        BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_RELATIVE_Y)) {
-        const ui::Transform rotation(transform.getOrientation());
-        const vec2 relativeXy = rotation.transform(getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
-                                                   getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
-        setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
-        setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
-    }
-
-    if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_ORIENTATION)) {
-        const float val = getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-        setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(transform, val));
-    }
-}
-
 // --- MotionEvent ---
 
 void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
@@ -723,13 +721,13 @@
 float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
                                              size_t historicalIndex) const {
     const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
-    return calculateTransformedAxisValue(axis, mSource, mRawTransform, coords);
+    return calculateTransformedAxisValue(axis, mSource, mFlags, mRawTransform, coords);
 }
 
 float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
                                           size_t historicalIndex) const {
     const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
-    return calculateTransformedAxisValue(axis, mSource, mTransform, coords);
+    return calculateTransformedAxisValue(axis, mSource, mFlags, mTransform, coords);
 }
 
 ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
@@ -786,8 +784,9 @@
     transform.set(matrix);
 
     // Apply the transformation to all samples.
-    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(),
-                  [&transform](PointerCoords& c) { c.transform(transform); });
+    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(), [&](PointerCoords& c) {
+        calculateTransformedCoordsInPlace(c, mSource, mFlags, transform);
+    });
 
     if (mRawXCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION &&
         mRawYCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION) {
@@ -1059,7 +1058,7 @@
 }
 
 // Keep in sync with calculateTransformedCoords.
-float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source,
+float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags,
                                                  const ui::Transform& transform,
                                                  const PointerCoords& coords) {
     if (shouldDisregardTransformation(source)) {
@@ -1081,7 +1080,7 @@
     }
 
     if (axis == AMOTION_EVENT_AXIS_ORIENTATION) {
-        return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+        return transformOrientation(transform, coords, flags);
     }
 
     return coords.getAxisValue(axis);
@@ -1089,29 +1088,32 @@
 
 // Keep in sync with calculateTransformedAxisValue. This is an optimization of
 // calculateTransformedAxisValue for all PointerCoords axes.
-PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source,
-                                                      const ui::Transform& transform,
-                                                      const PointerCoords& coords) {
+void MotionEvent::calculateTransformedCoordsInPlace(PointerCoords& coords, uint32_t source,
+                                                    int32_t flags, const ui::Transform& transform) {
     if (shouldDisregardTransformation(source)) {
-        return coords;
+        return;
     }
-    PointerCoords out = coords;
 
     const vec2 xy = calculateTransformedXYUnchecked(source, transform, coords.getXYValue());
-    out.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
-    out.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
 
     const vec2 relativeXy =
             transformWithoutTranslation(transform,
                                         {coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
                                          coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)});
-    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
-    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
 
-    out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
-                     transformAngle(transform,
-                                    coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)));
+    coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                        transformOrientation(transform, coords, flags));
+}
 
+PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, int32_t flags,
+                                                      const ui::Transform& transform,
+                                                      const PointerCoords& coords) {
+    PointerCoords out = coords;
+    calculateTransformedCoordsInPlace(out, source, flags, transform);
     return out;
 }
 
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index abc0392..fcf490d 100644
--- a/libs/input/InputConsumer.cpp
+++ b/libs/input/InputConsumer.cpp
@@ -181,7 +181,8 @@
 }
 
 bool shouldResampleTool(ToolType toolType) {
-    return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
+    return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
+            toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
 }
 
 } // namespace
@@ -592,6 +593,11 @@
             ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
             return;
         }
+        if (!shouldResampleTool(event->getToolType(i))) {
+            ALOGD_IF(debugResampling(),
+                     "Not resampled, containing unsupported tool type at pointer %d", id);
+            return;
+        }
     }
 
     // Find the data to use for resampling.
@@ -639,10 +645,18 @@
     }
 
     if (current->eventTime == sampleTime) {
-        // Prevents having 2 events with identical times and coordinates.
+        ALOGD_IF(debugResampling(), "Not resampled, 2 events with identical times.");
         return;
     }
 
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        if (!other->idBits.hasBit(id)) {
+            ALOGD_IF(debugResampling(), "Not resampled, the other doesn't have pointer id %d.", id);
+            return;
+        }
+    }
+
     // Resample touch coordinates.
     History oldLastResample;
     oldLastResample.initializeFrom(touchState.lastResample);
@@ -670,22 +684,16 @@
         const PointerCoords& currentCoords = current->getPointerById(id);
         resampledCoords = currentCoords;
         resampledCoords.isResampled = true;
-        if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
-            const PointerCoords& otherCoords = other->getPointerById(id);
-            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
-                                         lerp(currentCoords.getX(), otherCoords.getX(), alpha));
-            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                         lerp(currentCoords.getY(), otherCoords.getY(), alpha));
-            ALOGD_IF(debugResampling(),
-                     "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
-                     "other (%0.3f, %0.3f), alpha %0.3f",
-                     id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
-                     currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
-        } else {
-            ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
-                     resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
-                     currentCoords.getY());
-        }
+        const PointerCoords& otherCoords = other->getPointerById(id);
+        resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
+                                     lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+        resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                     lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+        ALOGD_IF(debugResampling(),
+                 "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+                 "other (%0.3f, %0.3f), alpha %0.3f",
+                 id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+                 currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
     }
 
     event->addSample(sampleTime, touchState.lastResample.pointers);
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index bc67810..9333ab8 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -273,10 +273,7 @@
 }
 
 void InputDeviceInfo::setKeyboardType(int32_t keyboardType) {
-    static_assert(AINPUT_KEYBOARD_TYPE_NONE < AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
-    static_assert(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC < AINPUT_KEYBOARD_TYPE_ALPHABETIC);
-    // There can be multiple subdevices with different keyboard types, set it to the highest type
-    mKeyboardType = std::max(mKeyboardType, keyboardType);
+    mKeyboardType = keyboardType;
 }
 
 void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) {
diff --git a/libs/input/KeyboardClassifier.cpp b/libs/input/KeyboardClassifier.cpp
new file mode 100644
index 0000000..0c2c7be
--- /dev/null
+++ b/libs/input/KeyboardClassifier.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "KeyboardClassifier"
+
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <ftl/flags.h>
+#include <input/KeyboardClassifier.h>
+
+#include "input_cxx_bridge.rs.h"
+
+namespace input_flags = com::android::input::flags;
+
+using android::input::RustInputDeviceIdentifier;
+
+namespace android {
+
+KeyboardClassifier::KeyboardClassifier() {
+    if (input_flags::enable_keyboard_classifier()) {
+        mRustClassifier = android::input::keyboardClassifier::create();
+    }
+}
+
+KeyboardType KeyboardClassifier::getKeyboardType(DeviceId deviceId) {
+    if (mRustClassifier) {
+        return static_cast<KeyboardType>(
+                android::input::keyboardClassifier::getKeyboardType(**mRustClassifier, deviceId));
+    } else {
+        auto it = mKeyboardTypeMap.find(deviceId);
+        if (it == mKeyboardTypeMap.end()) {
+            return KeyboardType::NONE;
+        }
+        return it->second;
+    }
+}
+
+// Copied from EventHub.h
+const uint32_t DEVICE_CLASS_KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD;
+const uint32_t DEVICE_CLASS_ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY;
+
+void KeyboardClassifier::notifyKeyboardChanged(DeviceId deviceId,
+                                               const InputDeviceIdentifier& identifier,
+                                               uint32_t deviceClasses) {
+    if (mRustClassifier) {
+        RustInputDeviceIdentifier rustIdentifier;
+        rustIdentifier.name = identifier.name;
+        rustIdentifier.location = identifier.location;
+        rustIdentifier.unique_id = identifier.uniqueId;
+        rustIdentifier.bus = identifier.bus;
+        rustIdentifier.vendor = identifier.vendor;
+        rustIdentifier.product = identifier.product;
+        rustIdentifier.version = identifier.version;
+        rustIdentifier.descriptor = identifier.descriptor;
+        android::input::keyboardClassifier::notifyKeyboardChanged(**mRustClassifier, deviceId,
+                                                                  rustIdentifier, deviceClasses);
+    } else {
+        bool isKeyboard = (deviceClasses & DEVICE_CLASS_KEYBOARD) != 0;
+        bool hasAlphabeticKey = (deviceClasses & DEVICE_CLASS_ALPHAKEY) != 0;
+        mKeyboardTypeMap.insert_or_assign(deviceId,
+                                          isKeyboard ? (hasAlphabeticKey
+                                                                ? KeyboardType::ALPHABETIC
+                                                                : KeyboardType::NON_ALPHABETIC)
+                                                     : KeyboardType::NONE);
+    }
+}
+
+void KeyboardClassifier::processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState) {
+    if (mRustClassifier &&
+        !android::input::keyboardClassifier::isFinalized(**mRustClassifier, deviceId)) {
+        android::input::keyboardClassifier::processKey(**mRustClassifier, deviceId, evdevCode,
+                                                       metaState);
+    }
+}
+
+} // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 90ed2b7..e23fc94 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,105 +49,24 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
-     * This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it and the event directly passed through
-     * the obscured area.
-     *
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     */
-    const int MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1;
-
-    /**
-     * This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it and the event did not directly pass
-     * through the obscured area.
-     *
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     *
-     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
-     * obstructed in areas other than the touched location.
-     */
-    const int MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
-
-    /**
-     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
-     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
-     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_HOVER_EXIT_PENDING = 0x4;
-
-    /**
-     * This flag indicates that the event has been generated by a gesture generator. It
-     * provides a hint to the GestureDetector to not apply any touch slop.
-     *
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8;
-
-    /**
-     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
-     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
-     * is set, the typical actions that occur in response for a pointer going up (such as click
-     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
-     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
-     * screen.
-     *
-     * @see #ACTION_POINTER_UP
-     * @see #ACTION_CANCEL
+     * Common input event flag used for both motion and key events for a gesture or pointer being
+     * canceled.
      */
     const int INPUT_EVENT_FLAG_CANCELED = 0x20;
 
     /**
-     * This flag indicates that the event will not cause a focus change if it is directed to an
-     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
-     * gestures to allow the user to direct gestures to an unfocused window without bringing the
-     * window into focus.
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40;
-
-    /**
-     * The input event was generated or modified by accessibility service.
-     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
-     * set of flags, including in input/Input.h and in android/input.h.
+     * Common input event flag used for both motion and key events, indicating that the event
+     * was generated or modified by accessibility service.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
     /**
-     * Private flag that indicates when the system has detected that this motion event
-     * may be inconsistent with respect to the sequence of previously delivered motion events,
-     * such as when a pointer move event is sent but the pointer is not down.
-     *
-     * @hide
-     * @see #isTainted
-     * @see #setTainted
+     * Common input event flag used for both motion and key events, indicating that the system has
+     * detected this event may be inconsistent with the current event sequence or gesture, such as
+     * when a pointer move event is sent but the pointer is not down.
      */
     const int INPUT_EVENT_FLAG_TAINTED = 0x80000000;
 
-    /**
-     * Private flag indicating that this event was synthesized by the system and should be delivered
-     * to the accessibility focused view first. When being dispatched such an event is not handled
-     * by predecessors of the accessibility focused view and after the event reaches that view the
-     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
-     * click on any view that has accessibility focus which is semantically equivalent to asking the
-     * view to perform a click accessibility action but more generic as views not implementing click
-     * action correctly can still be activated.
-     *
-     * @hide
-     * @see #isTargetAccessibilityFocus()
-     * @see #setTargetAccessibilityFocus(boolean)
-     */
-    const int MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
-
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
@@ -234,4 +153,157 @@
      * time to adjust to changes in direction.
      */
     const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
+
+
+    /*
+     * Input device class: Keyboard
+     * The input device is a keyboard or has buttons.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_KEYBOARD = 0x00000001;
+
+    /*
+     * Input device class: Alphakey
+     * The input device is an alpha-numeric keyboard (not just a dial pad).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_ALPHAKEY = 0x00000002;
+
+    /*
+     * Input device class: Touch
+     * The input device is a touchscreen or a touchpad (either single-touch or multi-touch).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_TOUCH = 0x00000004;
+
+    /*
+     * Input device class: Cursor
+     * The input device is a cursor device such as a trackball or mouse.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_CURSOR = 0x00000008;
+
+    /*
+     * Input device class: Multi-touch
+     * The input device is a multi-touch touchscreen or touchpad.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_TOUCH_MT = 0x00000010;
+
+    /*
+     * Input device class: Dpad
+     * The input device is a directional pad (implies keyboard, has DPAD keys).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_DPAD = 0x00000020;
+
+    /*
+     * Input device class: Gamepad
+     * The input device is a gamepad (implies keyboard, has BUTTON keys).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_GAMEPAD = 0x00000040;
+
+    /*
+     * Input device class: Switch
+     * The input device has switches.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_SWITCH = 0x00000080;
+
+    /*
+     * Input device class: Joystick
+     * The input device is a joystick (implies gamepad, has joystick absolute axes).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_JOYSTICK = 0x00000100;
+
+    /*
+     * Input device class: Vibrator
+     * The input device has a vibrator (supports FF_RUMBLE).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_VIBRATOR = 0x00000200;
+
+    /*
+     * Input device class: Mic
+     * The input device has a microphone.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_MIC = 0x00000400;
+
+    /*
+     * Input device class: External Stylus
+     * The input device is an external stylus (has data we want to fuse with touch data).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800;
+
+    /*
+     * Input device class: Rotary Encoder
+     * The input device has a rotary encoder.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_ROTARY_ENCODER = 0x00001000;
+
+    /*
+     * Input device class: Sensor
+     * The input device has a sensor like accelerometer, gyro, etc.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_SENSOR = 0x00002000;
+
+    /*
+     * Input device class: Battery
+     * The input device has a battery.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_BATTERY = 0x00004000;
+
+    /*
+     * Input device class: Light
+     * The input device has sysfs controllable lights.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_LIGHT = 0x00008000;
+
+    /*
+     * Input device class: Touchpad
+     * The input device is a touchpad, requiring an on-screen cursor.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_TOUCHPAD = 0x00010000;
+
+    /*
+     * Input device class: Virtual
+     * The input device is virtual (not a real device, not part of UI configuration).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_VIRTUAL = 0x20000000;
+
+    /*
+     * Input device class: External
+     * The input device is external (not built-in).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_EXTERNAL = 0x40000000;
 }
diff --git a/libs/input/android/os/MotionEventFlag.aidl b/libs/input/android/os/MotionEventFlag.aidl
new file mode 100644
index 0000000..2093b06
--- /dev/null
+++ b/libs/input/android/os/MotionEventFlag.aidl
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.IInputConstants;
+
+/**
+ * Flag definitions for MotionEvents.
+ * @hide
+ */
+@Backing(type="int")
+enum MotionEventFlag {
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event directly passed through
+     * the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    WINDOW_IS_OBSCURED = 0x1,
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event did not directly pass
+     * through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    HOVER_EXIT_PENDING = 0x4,
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    IS_GENERATED_GESTURE = 0x8,
+
+    /**
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
+     */
+    CANCELED = IInputConstants.INPUT_EVENT_FLAG_CANCELED,
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    NO_FOCUS_CHANGE = 0x40,
+
+    /**
+     * This flag indicates that the event has a valid value for AXIS_ORIENTATION.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80,
+
+    /**
+     * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine
+     * the direction in which the tool is pointing. The value of the orientation axis will be in
+     * the range [-pi, pi], which represents a full circle. This is usually supported by devices
+     * like styluses.
+     *
+     * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing
+     * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2],
+     * which represents half a circle. This is usually the case for devices like touchscreens and
+     * touchpads, for which it is difficult to tell which direction along the major axis of the
+     * touch ellipse the finger is pointing.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100,
+
+    /**
+     * The input event was generated or modified by accessibility service.
+     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
+     * set of flags, including in input/Input.h and in android/input.h.
+     */
+    IS_ACCESSIBILITY_EVENT = IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    TAINTED = IInputConstants.INPUT_EVENT_FLAG_TAINTED,
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    TARGET_ACCESSIBILITY_FOCUS = 0x40000000,
+}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 560166c..a2192cb 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -150,3 +150,10 @@
   description: "Hide touch and pointer indicators if a secure window is present on display"
   bug: "325252005"
 }
+
+flag {
+  name: "enable_keyboard_classifier"
+  namespace: "input"
+  description: "Keyboard classifier that classifies all keyboards into alphabetic or non-alphabetic"
+  bug: "263559234"
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index d0dbd6f..c46b7bb 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -16,22 +16,27 @@
 
 //! Common definitions of the Android Input Framework in rust.
 
+use crate::ffi::RustInputDeviceIdentifier;
 use bitflags::bitflags;
-use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED;
-use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
-use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED;
-use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING;
-use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
-use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
-use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS;
-use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
-use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+use inputconstants::aidl::android::os::IInputConstants;
+use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
 use std::fmt;
 
 /// The InputDevice ID.
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
 pub struct DeviceId(pub i32);
 
+/// The InputDevice equivalent for Rust inputflinger
+#[derive(Debug)]
+pub struct InputDevice {
+    /// InputDevice ID
+    pub device_id: DeviceId,
+    /// InputDevice unique identifier
+    pub identifier: RustInputDeviceIdentifier,
+    /// InputDevice classes (equivalent to EventHub InputDeviceClass)
+    pub classes: DeviceClass,
+}
+
 #[repr(u32)]
 pub enum SourceClass {
     None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
@@ -189,26 +194,34 @@
 
 bitflags! {
     /// MotionEvent flags.
+    /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
+    /// The flag values are redefined here as a bitflags API.
     #[derive(Debug)]
     pub struct MotionFlags: u32 {
         /// FLAG_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32;
+        const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED.0 as u32;
         /// FLAG_HOVER_EXIT_PENDING
-        const HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        const HOVER_EXIT_PENDING = MotionEventFlag::HOVER_EXIT_PENDING.0 as u32;
         /// FLAG_IS_GENERATED_GESTURE
-        const IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        const IS_GENERATED_GESTURE = MotionEventFlag::IS_GENERATED_GESTURE.0 as u32;
         /// FLAG_CANCELED
-        const CANCELED = INPUT_EVENT_FLAG_CANCELED as u32;
+        const CANCELED = MotionEventFlag::CANCELED.0 as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        const NO_FOCUS_CHANGE = MotionEventFlag::NO_FOCUS_CHANGE.0 as u32;
+        /// PRIVATE_FLAG_SUPPORTS_ORIENTATION
+        const PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION.0 as u32;
+        /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION
+        const PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
         /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
         /// FLAG_TAINTED
-        const TAINTED = INPUT_EVENT_FLAG_TAINTED as u32;
+        const TAINTED = MotionEventFlag::TAINTED.0 as u32;
         /// FLAG_TARGET_ACCESSIBILITY_FOCUS
-        const TARGET_ACCESSIBILITY_FOCUS = MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
+        const TARGET_ACCESSIBILITY_FOCUS = MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS.0 as u32;
     }
 }
 
@@ -220,13 +233,128 @@
     }
 }
 
+bitflags! {
+    /// Device class of the input device. These are duplicated from Eventhub.h
+    /// We need to make sure the two version remain in sync when adding new classes.
+    #[derive(Debug, PartialEq)]
+    pub struct DeviceClass: u32 {
+        /// The input device is a keyboard or has buttons
+        const Keyboard = IInputConstants::DEVICE_CLASS_KEYBOARD as u32;
+        /// The input device is an alpha-numeric keyboard (not just a dial pad)
+        const AlphabeticKey = IInputConstants::DEVICE_CLASS_ALPHAKEY as u32;
+        /// The input device is a touchscreen or a touchpad (either single-touch or multi-touch)
+        const Touch = IInputConstants::DEVICE_CLASS_TOUCH as u32;
+        /// The input device is a cursor device such as a trackball or mouse.
+        const Cursor = IInputConstants::DEVICE_CLASS_CURSOR as u32;
+        /// The input device is a multi-touch touchscreen or touchpad.
+        const MultiTouch = IInputConstants::DEVICE_CLASS_TOUCH_MT as u32;
+        /// The input device is a directional pad (implies keyboard, has DPAD keys).
+        const Dpad = IInputConstants::DEVICE_CLASS_DPAD as u32;
+        /// The input device is a gamepad (implies keyboard, has BUTTON keys).
+        const Gamepad = IInputConstants::DEVICE_CLASS_GAMEPAD as u32;
+        /// The input device has switches.
+        const Switch = IInputConstants::DEVICE_CLASS_SWITCH as u32;
+        /// The input device is a joystick (implies gamepad, has joystick absolute axes).
+        const Joystick = IInputConstants::DEVICE_CLASS_JOYSTICK as u32;
+        /// The input device has a vibrator (supports FF_RUMBLE).
+        const Vibrator = IInputConstants::DEVICE_CLASS_VIBRATOR as u32;
+        /// The input device has a microphone.
+        const Mic = IInputConstants::DEVICE_CLASS_MIC as u32;
+        /// The input device is an external stylus (has data we want to fuse with touch data).
+        const ExternalStylus = IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS as u32;
+        /// The input device has a rotary encoder
+        const RotaryEncoder = IInputConstants::DEVICE_CLASS_ROTARY_ENCODER as u32;
+        /// The input device has a sensor like accelerometer, gyro, etc
+        const Sensor = IInputConstants::DEVICE_CLASS_SENSOR as u32;
+        /// The input device has a battery
+        const Battery = IInputConstants::DEVICE_CLASS_BATTERY as u32;
+        /// The input device has sysfs controllable lights
+        const Light = IInputConstants::DEVICE_CLASS_LIGHT as u32;
+        /// The input device is a touchpad, requiring an on-screen cursor.
+        const Touchpad = IInputConstants::DEVICE_CLASS_TOUCHPAD as u32;
+        /// The input device is virtual (not a real device, not part of UI configuration).
+        const Virtual = IInputConstants::DEVICE_CLASS_VIRTUAL as u32;
+        /// The input device is external (not built-in).
+        const External = IInputConstants::DEVICE_CLASS_EXTERNAL as u32;
+    }
+}
+
+bitflags! {
+    /// Modifier state flags
+    #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
+    pub struct ModifierState: u32 {
+        /// No meta keys are pressed
+        const None = input_bindgen::AMETA_NONE;
+        /// This mask is used to check whether one of the ALT meta keys is pressed
+        const AltOn = input_bindgen::AMETA_ALT_ON;
+        /// This mask is used to check whether the left ALT meta key is pressed
+        const AltLeftOn = input_bindgen::AMETA_ALT_LEFT_ON;
+        /// This mask is used to check whether the right ALT meta key is pressed
+        const AltRightOn = input_bindgen::AMETA_ALT_RIGHT_ON;
+        /// This mask is used to check whether one of the SHIFT meta keys is pressed
+        const ShiftOn = input_bindgen::AMETA_SHIFT_ON;
+        /// This mask is used to check whether the left SHIFT meta key is pressed
+        const ShiftLeftOn = input_bindgen::AMETA_SHIFT_LEFT_ON;
+        /// This mask is used to check whether the right SHIFT meta key is pressed
+        const ShiftRightOn = input_bindgen::AMETA_SHIFT_RIGHT_ON;
+        /// This mask is used to check whether the SYM meta key is pressed
+        const SymOn = input_bindgen::AMETA_SYM_ON;
+        /// This mask is used to check whether the FUNCTION meta key is pressed
+        const FunctionOn = input_bindgen::AMETA_FUNCTION_ON;
+        /// This mask is used to check whether one of the CTRL meta keys is pressed
+        const CtrlOn = input_bindgen::AMETA_CTRL_ON;
+        /// This mask is used to check whether the left CTRL meta key is pressed
+        const CtrlLeftOn = input_bindgen::AMETA_CTRL_LEFT_ON;
+        /// This mask is used to check whether the right CTRL meta key is pressed
+        const CtrlRightOn = input_bindgen::AMETA_CTRL_RIGHT_ON;
+        /// This mask is used to check whether one of the META meta keys is pressed
+        const MetaOn = input_bindgen::AMETA_META_ON;
+        /// This mask is used to check whether the left META meta key is pressed
+        const MetaLeftOn = input_bindgen::AMETA_META_LEFT_ON;
+        /// This mask is used to check whether the right META meta key is pressed
+        const MetaRightOn = input_bindgen::AMETA_META_RIGHT_ON;
+        /// This mask is used to check whether the CAPS LOCK meta key is on
+        const CapsLockOn = input_bindgen::AMETA_CAPS_LOCK_ON;
+        /// This mask is used to check whether the NUM LOCK meta key is on
+        const NumLockOn = input_bindgen::AMETA_NUM_LOCK_ON;
+        /// This mask is used to check whether the SCROLL LOCK meta key is on
+        const ScrollLockOn = input_bindgen::AMETA_SCROLL_LOCK_ON;
+    }
+}
+
+/// A rust enum representation of a Keyboard type.
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum KeyboardType {
+    /// KEYBOARD_TYPE_NONE
+    None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE,
+    /// KEYBOARD_TYPE_NON_ALPHABETIC
+    NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+    /// KEYBOARD_TYPE_ALPHABETIC
+    Alphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+}
+
 #[cfg(test)]
 mod tests {
     use crate::input::SourceClass;
+    use crate::MotionFlags;
     use crate::Source;
+    use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
+
     #[test]
     fn convert_source_class_pointer() {
         let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
         assert!(source.is_from_class(SourceClass::Pointer));
     }
+
+    /// Ensure all MotionEventFlag constants are re-defined in rust.
+    #[test]
+    fn all_motion_event_flags_defined() {
+        for flag in MotionEventFlag::enum_values() {
+            assert!(
+                MotionFlags::from_bits(flag.0 as u32).is_some(),
+                "MotionEventFlag value {flag:?} is not redefined as a rust MotionFlags"
+            );
+        }
+    }
 }
diff --git a/libs/input/rust/keyboard_classification_config.rs b/libs/input/rust/keyboard_classification_config.rs
new file mode 100644
index 0000000..ab74efb
--- /dev/null
+++ b/libs/input/rust/keyboard_classification_config.rs
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::input::KeyboardType;
+
+// TODO(b/263559234): Categorize some of these to KeyboardType::None based on ability to produce
+//  key events at all. (Requires setup allowing InputDevice to dynamically add/remove
+//  KeyboardInputMapper based on blocklist and KeyEvents in case a KeyboardType::None device ends
+//  up producing a key event)
+pub static CLASSIFIED_DEVICES: &[(
+    /* vendorId */ u16,
+    /* productId */ u16,
+    KeyboardType,
+    /* is_finalized */ bool,
+)] = &[
+    // HP X4000 Wireless Mouse
+    (0x03f0, 0xa407, KeyboardType::NonAlphabetic, true),
+    // Microsoft Wireless Mobile Mouse 6000
+    (0x045e, 0x0745, KeyboardType::NonAlphabetic, true),
+    // Microsoft Surface Precision Mouse
+    (0x045e, 0x0821, KeyboardType::NonAlphabetic, true),
+    // Microsoft Pro IntelliMouse
+    (0x045e, 0x082a, KeyboardType::NonAlphabetic, true),
+    // Microsoft Bluetooth Mouse
+    (0x045e, 0x082f, KeyboardType::NonAlphabetic, true),
+    // Xbox One Elite Series 2 gamepad
+    (0x045e, 0x0b05, KeyboardType::NonAlphabetic, true),
+    // Logitech T400
+    (0x046d, 0x4026, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Unifying)
+    (0x046d, 0x405e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Unifying)
+    (0x046d, 0x4069, KeyboardType::NonAlphabetic, true),
+    // Logitech M585 (Unifying)
+    (0x046d, 0x406b, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Unifying)
+    (0x046d, 0x4072, KeyboardType::NonAlphabetic, true),
+    // Logitech Pebble M350
+    (0x046d, 0x4080, KeyboardType::NonAlphabetic, true),
+    // Logitech T630 Ultrathin
+    (0x046d, 0xb00d, KeyboardType::NonAlphabetic, true),
+    // Logitech M558
+    (0x046d, 0xb011, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb012, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb013, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Bluetooth)
+    (0x046d, 0xb015, KeyboardType::NonAlphabetic, true),
+    // Logitech M535
+    (0x046d, 0xb016, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master / Anywhere 2 (Bluetooth)
+    (0x046d, 0xb017, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Bluetooth)
+    (0x046d, 0xb019, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2S (Bluetooth)
+    (0x046d, 0xb01a, KeyboardType::NonAlphabetic, true),
+    // Logitech M585/M590 (Bluetooth)
+    (0x046d, 0xb01b, KeyboardType::NonAlphabetic, true),
+    // Logitech G603 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb01c, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb01e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb01f, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 3 (Bluetooth)
+    (0x046d, 0xb023, KeyboardType::NonAlphabetic, true),
+    // Logitech G604 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb024, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (Bluetooth)
+    (0x046d, 0xb503, KeyboardType::NonAlphabetic, true),
+    // Logitech R500 (Bluetooth)
+    (0x046d, 0xb505, KeyboardType::NonAlphabetic, true),
+    // Logitech M500s
+    (0x046d, 0xc093, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (USB dongle)
+    (0x046d, 0xc53e, KeyboardType::NonAlphabetic, true),
+    // Elecom Enelo IR LED Mouse 350
+    (0x056e, 0x0134, KeyboardType::NonAlphabetic, true),
+    // Elecom EPRIM Blue LED 5 button mouse 228
+    (0x056e, 0x0141, KeyboardType::NonAlphabetic, true),
+    // Elecom Blue LED Mouse 203
+    (0x056e, 0x0159, KeyboardType::NonAlphabetic, true),
+    // Zebra LS2208 barcode scanner
+    (0x05e0, 0x1200, KeyboardType::NonAlphabetic, true),
+    // RDing FootSwitch1F1
+    (0x0c45, 0x7403, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Sensei RAW Frost Blue
+    (0x1038, 0x1369, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wired
+    (0x1038, 0x1824, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wireless (USB dongle)
+    (0x1038, 0x1830, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey
+    (0x1050, 0x0010, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey 4 OTP+U2F+CCID
+    (0x1050, 0x0407, KeyboardType::NonAlphabetic, true),
+    // Lenovo USB-C Wired Compact Mouse
+    (0x17ef, 0x6123, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (USB dongle)
+    (0x1b1c, 0x1b94, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (Bluetooth)
+    (0x1bae, 0x1b1c, KeyboardType::NonAlphabetic, true),
+    // Kensington Pro Fit Full-size
+    (0x1bcf, 0x08a0, KeyboardType::NonAlphabetic, true),
+    // Huion HS64
+    (0x256c, 0x006d, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Star G640
+    (0x28bd, 0x0914, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Artist 12 Pro
+    (0x28bd, 0x091f, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Deco mini7W
+    (0x28bd, 0x0928, KeyboardType::NonAlphabetic, true),
+];
diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs
new file mode 100644
index 0000000..8721ef7
--- /dev/null
+++ b/libs/input/rust/keyboard_classifier.rs
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Contains the KeyboardClassifier, that tries to identify whether an Input device is an
+//! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device
+//! in order to verify/change the inferred keyboard type.
+//!
+//! Initial classification:
+//! - If DeviceClass includes Dpad, Touch, Cursor, MultiTouch, ExternalStylus, Touchpad, Dpad,
+//!   Gamepad, Switch, Joystick, RotaryEncoder => KeyboardType::NonAlphabetic
+//! - Otherwise if DeviceClass has Keyboard and not AlphabeticKey => KeyboardType::NonAlphabetic
+//! - Otherwise if DeviceClass has both Keyboard and AlphabeticKey => KeyboardType::Alphabetic
+//!
+//! On process keys:
+//! - If KeyboardType::NonAlphabetic and we receive alphabetic key event, then change type to
+//!   KeyboardType::Alphabetic. Once changed, no further changes. (i.e. verified = true)
+//! - TODO(b/263559234): If KeyboardType::Alphabetic and we don't receive any alphabetic key event
+//!    across multiple device connections in a time period, then change type to
+//!    KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic
+//!    (i.e. verified = false).
+//!
+//! TODO(b/263559234): Data store implementation to store information about past classification
+
+use crate::input::{DeviceId, InputDevice, KeyboardType};
+use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
+use crate::{DeviceClass, ModifierState};
+use std::collections::HashMap;
+
+/// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic
+/// keyboard or non-alphabetic keyboard
+#[derive(Default)]
+pub struct KeyboardClassifier {
+    device_map: HashMap<DeviceId, KeyboardInfo>,
+}
+
+struct KeyboardInfo {
+    _device: InputDevice,
+    keyboard_type: KeyboardType,
+    is_finalized: bool,
+}
+
+impl KeyboardClassifier {
+    /// Create a new KeyboardClassifier
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Adds keyboard to KeyboardClassifier
+    pub fn notify_keyboard_changed(&mut self, device: InputDevice) {
+        let (keyboard_type, is_finalized) = self.classify_keyboard(&device);
+        self.device_map.insert(
+            device.device_id,
+            KeyboardInfo { _device: device, keyboard_type, is_finalized },
+        );
+    }
+
+    /// Get keyboard type for a tracked keyboard in KeyboardClassifier
+    pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType {
+        return if let Some(keyboard) = self.device_map.get(&device_id) {
+            keyboard.keyboard_type
+        } else {
+            KeyboardType::None
+        };
+    }
+
+    /// Tells if keyboard type classification is finalized. Once finalized the classification can't
+    /// change until device is reconnected again.
+    ///
+    /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or
+    /// allowlist that are explicitly categorized and won't change with future key events
+    pub fn is_finalized(&self, device_id: DeviceId) -> bool {
+        return if let Some(keyboard) = self.device_map.get(&device_id) {
+            keyboard.is_finalized
+        } else {
+            false
+        };
+    }
+
+    /// Process a key event and change keyboard type if required.
+    /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic
+    /// - If an alphabetic key occurs, the keyboard type will change to Alphabetic
+    pub fn process_key(
+        &mut self,
+        device_id: DeviceId,
+        evdev_code: i32,
+        modifier_state: ModifierState,
+    ) {
+        if let Some(keyboard) = self.device_map.get_mut(&device_id) {
+            // Ignore all key events with modifier state since they can be macro shortcuts used by
+            // some non-keyboard peripherals like TV remotes, game controllers, etc.
+            if modifier_state.bits() != 0 {
+                return;
+            }
+            if Self::is_alphabetic_key(&evdev_code) {
+                keyboard.keyboard_type = KeyboardType::Alphabetic;
+                keyboard.is_finalized = true;
+            }
+        }
+    }
+
+    fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) {
+        // This should never happen but having keyboard device class is necessary to be classified
+        // as any type of keyboard.
+        if !device.classes.contains(DeviceClass::Keyboard) {
+            return (KeyboardType::None, true);
+        }
+        // Normal classification for internal and virtual keyboards
+        if !device.classes.contains(DeviceClass::External)
+            || device.classes.contains(DeviceClass::Virtual)
+        {
+            return if device.classes.contains(DeviceClass::AlphabeticKey) {
+                (KeyboardType::Alphabetic, true)
+            } else {
+                (KeyboardType::NonAlphabetic, true)
+            };
+        }
+
+        // Check in known device list for classification
+        for data in CLASSIFIED_DEVICES.iter() {
+            if device.identifier.vendor == data.0 && device.identifier.product == data.1 {
+                return (data.2, data.3);
+            }
+        }
+
+        // Any composite device with multiple device classes should be categorized as non-alphabetic
+        // keyboard initially
+        if device.classes.contains(DeviceClass::Touch)
+            || device.classes.contains(DeviceClass::Cursor)
+            || device.classes.contains(DeviceClass::MultiTouch)
+            || device.classes.contains(DeviceClass::ExternalStylus)
+            || device.classes.contains(DeviceClass::Touchpad)
+            || device.classes.contains(DeviceClass::Dpad)
+            || device.classes.contains(DeviceClass::Gamepad)
+            || device.classes.contains(DeviceClass::Switch)
+            || device.classes.contains(DeviceClass::Joystick)
+            || device.classes.contains(DeviceClass::RotaryEncoder)
+        {
+            // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
+            // kernel, we no longer need to process key events to verify.
+            return (
+                KeyboardType::NonAlphabetic,
+                !device.classes.contains(DeviceClass::AlphabeticKey),
+            );
+        }
+        // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard
+        if device.classes.contains(DeviceClass::AlphabeticKey) {
+            (KeyboardType::Alphabetic, true)
+        } else {
+            // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
+            // kernel, we no longer need to process key events to verify.
+            (KeyboardType::NonAlphabetic, true)
+        }
+    }
+
+    fn is_alphabetic_key(evdev_code: &i32) -> bool {
+        // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ])
+        (16..=27).contains(evdev_code)
+            // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `)
+            || (30..=41).contains(evdev_code)
+            // Keyboard alphabetic row 3 (\ Z X C V B N M , . /)
+            || (43..=53).contains(evdev_code)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input::{DeviceId, InputDevice, KeyboardType};
+    use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
+    use crate::keyboard_classifier::KeyboardClassifier;
+    use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
+
+    static DEVICE_ID: DeviceId = DeviceId(1);
+    static KEY_A: i32 = 30;
+    static KEY_1: i32 = 2;
+
+    #[test]
+    fn classify_external_alphabetic_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
+        assert!(classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_external_non_alphabetic_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier
+            .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_mouse_pretending_as_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Cursor
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_touchpad_pretending_as_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Touchpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_stylus_pretending_as_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::ExternalStylus
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_dpad_pretending_as_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_joystick_pretending_as_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Joystick
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_gamepad_pretending_as_keyboard() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Gamepad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn reclassify_keyboard_on_alphabetic_key_event() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+
+        // on alphabetic key event
+        classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
+        assert!(classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+
+        // on number key event
+        classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None);
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
+        let mut classifier = KeyboardClassifier::new();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+
+        classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn);
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_known_devices() {
+        let mut classifier = KeyboardClassifier::new();
+        for device in CLASSIFIED_DEVICES.iter() {
+            classifier
+                .notify_keyboard_changed(create_device_with_vendor_product_ids(device.0, device.1));
+            assert_eq!(classifier.get_keyboard_type(DEVICE_ID), device.2);
+            assert_eq!(classifier.is_finalized(DEVICE_ID), device.3);
+        }
+    }
+
+    fn create_device(classes: DeviceClass) -> InputDevice {
+        InputDevice {
+            device_id: DEVICE_ID,
+            identifier: RustInputDeviceIdentifier {
+                name: "test_device".to_string(),
+                location: "location".to_string(),
+                unique_id: "unique_id".to_string(),
+                bus: 123,
+                vendor: 234,
+                product: 345,
+                version: 567,
+                descriptor: "descriptor".to_string(),
+            },
+            classes,
+        }
+    }
+
+    fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice {
+        InputDevice {
+            device_id: DEVICE_ID,
+            identifier: RustInputDeviceIdentifier {
+                name: "test_device".to_string(),
+                location: "location".to_string(),
+                unique_id: "unique_id".to_string(),
+                bus: 123,
+                vendor,
+                product,
+                version: 567,
+                descriptor: "descriptor".to_string(),
+            },
+            classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
+        }
+    }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index fb3f520..af8f889 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -18,9 +18,14 @@
 
 mod input;
 mod input_verifier;
+mod keyboard_classification_config;
+mod keyboard_classifier;
 
-pub use input::{DeviceId, MotionAction, MotionFlags, Source};
+pub use input::{
+    DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source,
+};
 pub use input_verifier::InputVerifier;
+pub use keyboard_classifier::KeyboardClassifier;
 
 #[cxx::bridge(namespace = "android::input")]
 #[allow(unsafe_op_in_unsafe_fn)]
@@ -47,7 +52,8 @@
         /// }
         /// ```
         type InputVerifier;
-        fn create(name: String) -> Box<InputVerifier>;
+        #[cxx_name = create]
+        fn create_input_verifier(name: String) -> Box<InputVerifier>;
         fn process_movement(
             verifier: &mut InputVerifier,
             device_id: i32,
@@ -59,15 +65,53 @@
         fn reset_device(verifier: &mut InputVerifier, device_id: i32);
     }
 
+    #[namespace = "android::input::keyboardClassifier"]
+    extern "Rust" {
+        /// Used to classify a keyboard into alphabetic and non-alphabetic
+        type KeyboardClassifier;
+        #[cxx_name = create]
+        fn create_keyboard_classifier() -> Box<KeyboardClassifier>;
+        #[cxx_name = notifyKeyboardChanged]
+        fn notify_keyboard_changed(
+            classifier: &mut KeyboardClassifier,
+            device_id: i32,
+            identifier: RustInputDeviceIdentifier,
+            device_classes: u32,
+        );
+        #[cxx_name = getKeyboardType]
+        fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32;
+        #[cxx_name = isFinalized]
+        fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool;
+        #[cxx_name = processKey]
+        fn process_key(
+            classifier: &mut KeyboardClassifier,
+            device_id: i32,
+            evdev_code: i32,
+            modifier_state: u32,
+        );
+    }
+
     #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
     pub struct RustPointerProperties {
         pub id: i32,
     }
+
+    #[derive(Debug)]
+    pub struct RustInputDeviceIdentifier {
+        pub name: String,
+        pub location: String,
+        pub unique_id: String,
+        pub bus: u16,
+        pub vendor: u16,
+        pub product: u16,
+        pub version: u16,
+        pub descriptor: String,
+    }
 }
 
-use crate::ffi::RustPointerProperties;
+use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties};
 
-fn create(name: String) -> Box<InputVerifier> {
+fn create_input_verifier(name: String) -> Box<InputVerifier> {
     Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
 }
 
@@ -103,3 +147,53 @@
 fn reset_device(verifier: &mut InputVerifier, device_id: i32) {
     verifier.reset_device(DeviceId(device_id));
 }
+
+fn create_keyboard_classifier() -> Box<KeyboardClassifier> {
+    Box::new(KeyboardClassifier::new())
+}
+
+fn notify_keyboard_changed(
+    classifier: &mut KeyboardClassifier,
+    device_id: i32,
+    identifier: RustInputDeviceIdentifier,
+    device_classes: u32,
+) {
+    let classes = DeviceClass::from_bits(device_classes);
+    if classes.is_none() {
+        panic!(
+            "The conversion of device class 0x{:08x} failed, please check if some device classes
+             have not been added to DeviceClass.",
+            device_classes
+        );
+    }
+    classifier.notify_keyboard_changed(InputDevice {
+        device_id: DeviceId(device_id),
+        identifier,
+        classes: classes.unwrap(),
+    });
+}
+
+fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32 {
+    classifier.get_keyboard_type(DeviceId(device_id)) as u32
+}
+
+fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool {
+    classifier.is_finalized(DeviceId(device_id))
+}
+
+fn process_key(
+    classifier: &mut KeyboardClassifier,
+    device_id: i32,
+    evdev_code: i32,
+    meta_state: u32,
+) {
+    let modifier_state = ModifierState::from_bits(meta_state);
+    if modifier_state.is_none() {
+        panic!(
+            "The conversion of meta state 0x{:08x} failed, please check if some meta state
+             have not been added to ModifierState.",
+            meta_state
+        );
+    }
+    classifier.process_key(DeviceId(device_id), evdev_code, modifier_state.unwrap());
+}
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 476b5cf..3717f49 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -26,23 +26,39 @@
 
 namespace android {
 
+namespace {
+
 // Default display id.
-static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 
-static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
 
-static constexpr auto POINTER_0_DOWN =
+constexpr auto POINTER_0_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-static constexpr auto POINTER_1_DOWN =
+constexpr auto POINTER_1_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-static constexpr auto POINTER_0_UP =
+constexpr auto POINTER_0_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-static constexpr auto POINTER_1_UP =
+constexpr auto POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
+std::array<float, 9> asFloat9(const ui::Transform& t) {
+    std::array<float, 9> mat{};
+    mat[0] = t[0][0];
+    mat[1] = t[1][0];
+    mat[2] = t[2][0];
+    mat[3] = t[0][1];
+    mat[4] = t[1][1];
+    mat[5] = t[2][1];
+    mat[6] = t[0][2];
+    mat[7] = t[1][2];
+    mat[8] = t[2][2];
+    return mat;
+}
+
 class BaseTest : public testing::Test {
 protected:
     static constexpr std::array<uint8_t, 32> HMAC = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
@@ -50,6 +66,8 @@
                                                      22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
 };
 
+} // namespace
+
 // --- PointerCoordsTest ---
 
 class PointerCoordsTest : public BaseTest {
@@ -344,13 +362,15 @@
 }
 
 void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
+    const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     event->initialize(mId, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC,
-                      AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
-                      AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
-                      MotionClassification::NONE, mTransform, 2.0f, 2.1f,
-                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                      mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
-                      mPointerProperties, mSamples[0].pointerCoords);
+                      AMOTION_EVENT_ACTION_MOVE, 0, flags, AMOTION_EVENT_EDGE_FLAG_TOP,
+                      AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, MotionClassification::NONE,
+                      mTransform, 2.0f, 2.1f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                      AMOTION_EVENT_INVALID_CURSOR_POSITION, mRawTransform, ARBITRARY_DOWN_TIME,
+                      ARBITRARY_EVENT_TIME, 2, mPointerProperties, mSamples[0].pointerCoords);
     event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords);
     event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords);
 }
@@ -364,7 +384,10 @@
     ASSERT_EQ(DISPLAY_ID, event->getDisplayId());
     EXPECT_EQ(HMAC, event->getHmac());
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, event->getAction());
-    ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, event->getFlags());
+    ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+                      AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+                      AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
+              event->getFlags());
     ASSERT_EQ(AMOTION_EVENT_EDGE_FLAG_TOP, event->getEdgeFlags());
     ASSERT_EQ(AMETA_ALT_ON, event->getMetaState());
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState());
@@ -799,8 +822,10 @@
     }
     MotionEvent event;
     ui::Transform identityTransform;
+    const int32_t flags = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID,
-                     INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, /*flags=*/0,
+                     INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, flags,
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
                      MotionClassification::NONE, identityTransform, /*xPrecision=*/0,
                      /*yPrecision=*/0, /*xCursorPosition=*/3 + RADIUS, /*yCursorPosition=*/2,
@@ -1087,4 +1112,90 @@
     ASSERT_EQ(EXPECTED.y, event.getYCursorPosition());
 }
 
+TEST_F(MotionEventTest, InvalidOrientationNotRotated) {
+    // This touch event does not have a value for AXIS_ORIENTATION, and the flags are implicitly
+    // set to 0. The transform is set to a 90-degree rotation.
+    MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                                .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                                .build();
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+    event.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+    event.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+    event.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+}
+
+TEST_F(MotionEventTest, ValidZeroOrientationRotated) {
+    // This touch events will implicitly have a value of 0 for its AXIS_ORIENTATION.
+    auto builder = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                           .downTime(ARBITRARY_DOWN_TIME)
+                           .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                           .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                           .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                           .addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION);
+    MotionEvent nonDirectionalEvent = builder.build();
+    MotionEvent directionalEvent =
+            builder.addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION).build();
+
+    // The angle is rotated by the initial transform, a 90-degree rotation.
+    ASSERT_NEAR(fabs(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), M_PI_2, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON);
+    ASSERT_NEAR(fabs(directionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON);
+
+    nonDirectionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    directionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    ASSERT_NEAR(fabs(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), -M_PI_2, EPSILON);
+}
+
+TEST_F(MotionEventTest, ValidNonZeroOrientationRotated) {
+    const float initial = 1.f;
+    auto builder = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                           .downTime(ARBITRARY_DOWN_TIME)
+                           .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER)
+                                            .x(4)
+                                            .y(4)
+                                            .axis(AMOTION_EVENT_AXIS_ORIENTATION, initial))
+                           .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                           .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                           .addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION);
+
+    MotionEvent nonDirectionalEvent = builder.build();
+    MotionEvent directionalEvent =
+            builder.addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION).build();
+
+    // The angle is rotated by the initial transform, a 90-degree rotation.
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial + M_PI_2, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON);
+
+    nonDirectionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    directionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON);
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index 70529bb..f49469c 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -96,7 +96,9 @@
     hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
             16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
 
-    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     if (action == AMOTION_EVENT_ACTION_CANCEL) {
         flags |= AMOTION_EVENT_FLAG_CANCELED;
     }
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 48512f7..e65a919 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -89,7 +89,9 @@
     hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
             16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
 
-    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     if (action == AMOTION_EVENT_ACTION_CANCEL) {
         flags |= AMOTION_EVENT_FLAG_CANCELED;
     }
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 2dc9fdb..8d8b530 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -297,10 +297,9 @@
 }
 
 /**
- * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
- * a resampled timestamp and should be marked as such.
+ * Stylus pointer coordinates are resampled.
  */
-TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+TEST_F(TouchResamplingTest, StylusEventIsResampled) {
     std::chrono::nanoseconds frameTime;
     std::vector<InputEventEntry> entries, expectedEntries;
 
@@ -330,15 +329,91 @@
             //      id  x   y
             {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
             {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
-            // A resampled event is generated, but the stylus coordinates are not resampled.
             {25ms,
-             {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+             {{0, 35, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
              AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
 
 /**
+ * Mouse pointer coordinates are resampled.
+ */
+TEST_F(TouchResamplingTest, MouseEventIsResampled) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+            {25ms,
+             {{0, 35, 30, .toolType = ToolType::MOUSE, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
+ * Motion events with palm tool type are not resampled.
+ */
+TEST_F(TouchResamplingTest, PalmEventIsNotResampled) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
  * Event should not be resampled when sample time is equal to event time.
  */
 TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) {
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 757d935..4a04467 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -50,6 +50,7 @@
         "libshaders",
         "libtonemap",
         "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 1c60563..bc3976d 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -22,24 +22,49 @@
 #include "skia/SkiaGLRenderEngine.h"
 #include "threaded/RenderEngineThreaded.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <cutils/properties.h>
 #include <log/log.h>
 
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE) || \
+        COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(FORCE_COMPILE_GRAPHITE_RENDERENGINE)
+#define COMPILE_GRAPHITE_RENDERENGINE 1
+#else
+#define COMPILE_GRAPHITE_RENDERENGINE 0
+#endif
+
 namespace android {
 namespace renderengine {
 
 std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
     threaded::CreateInstanceFactory createInstanceFactory;
 
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COMPILE_GRAPHITE_RENDERENGINE
+    const RenderEngine::SkiaBackend actualSkiaBackend = args.skiaBackend;
+#else
+    if (args.skiaBackend == RenderEngine::SkiaBackend::GRAPHITE) {
+        ALOGE("RenderEngine with Graphite Skia backend was requested, but Graphite was not "
+              "included in the build. Falling back to Ganesh (%s)",
+              args.graphicsApi == RenderEngine::GraphicsApi::GL ? "GL" : "Vulkan");
+    }
+    const RenderEngine::SkiaBackend actualSkiaBackend = RenderEngine::SkiaBackend::GANESH;
+#endif
+
     ALOGD("%sRenderEngine with %s Backend (%s)", args.threaded == Threaded::YES ? "Threaded " : "",
           args.graphicsApi == GraphicsApi::GL ? "SkiaGL" : "SkiaVK",
-          args.skiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite");
+          actualSkiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite");
 
-    if (args.skiaBackend == SkiaBackend::GRAPHITE) {
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COMPILE_GRAPHITE_RENDERENGINE
+    if (actualSkiaBackend == SkiaBackend::GRAPHITE) {
         createInstanceFactory = [args]() {
             return android::renderengine::skia::GraphiteVkRenderEngine::create(args);
         };
-    } else { // GANESH
+    } else
+#endif
+    { // GANESH
         if (args.graphicsApi == GraphicsApi::VK) {
             createInstanceFactory = [args]() {
                 return android::renderengine::skia::GaneshVkRenderEngine::create(args);
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index bd50107..a1f917d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -29,7 +29,6 @@
 #include <GrDirectContext.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
-#include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
 
 #include <android-base/stringprintf.h>
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 0a2f9b2..d2bb3d5 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -17,8 +17,6 @@
 #ifndef SF_SKIAVKRENDERENGINE_H_
 #define SF_SKIAVKRENDERENGINE_H_
 
-#include <vk/GrVkBackendContext.h>
-
 #include "SkiaRenderEngine.h"
 #include "VulkanInterface.h"
 #include "compat/SkiaGpuContext.h"
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index fc16c56..37b69f6 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -32,21 +32,8 @@
 namespace renderengine {
 namespace skia {
 
-GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
-    GrVkBackendContext backendContext;
-    backendContext.fInstance = mInstance;
-    backendContext.fPhysicalDevice = mPhysicalDevice;
-    backendContext.fDevice = mDevice;
-    backendContext.fQueue = mQueue;
-    backendContext.fGraphicsQueueIndex = mQueueIndex;
-    backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
-    backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
-    backendContext.fGetProc = mGrGetProc;
-    backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
-    backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
-    backendContext.fDeviceLostProc = onVkDeviceFault;
-    return backendContext;
+VulkanBackendContext VulkanInterface::getGaneshBackendContext() {
+    return this->getGraphiteBackendContext();
 };
 
 VulkanBackendContext VulkanInterface::getGraphiteBackendContext() {
@@ -57,7 +44,7 @@
     backendContext.fQueue = mQueue;
     backendContext.fGraphicsQueueIndex = mQueueIndex;
     backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
+    backendContext.fVkExtensions = &mVulkanExtensions;
     backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
     backendContext.fGetProc = mGrGetProc;
     backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
@@ -197,7 +184,9 @@
     LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
 };
 
-static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+static skgpu::VulkanGetProc sGetProc = [](const char* proc_name,
+                                          VkInstance instance,
+                                          VkDevice device) {
     if (device != VK_NULL_HANDLE) {
         return vkGetDeviceProcAddr(device, proc_name);
     }
@@ -427,11 +416,11 @@
         mDeviceExtensionNames.push_back(devExt.extensionName);
     }
 
-    mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
-                       enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
-                       enabledDeviceExtensionNames.data());
+    mVulkanExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+                           enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+                           enabledDeviceExtensionNames.data());
 
-    if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+    if (!mVulkanExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
         BAIL("Vulkan driver doesn't support external semaphore fd");
     }
 
@@ -456,7 +445,7 @@
         tailPnext = &mProtectedMemoryFeatures->pNext;
     }
 
-    if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
         mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
         mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
         mDeviceFaultFeatures->pNext = nullptr;
@@ -482,7 +471,7 @@
             queuePriority,
     };
 
-    if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
         queueNextPtr = &queuePriorityCreateInfo;
     }
 
@@ -604,7 +593,7 @@
     mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
     mQueueIndex = 0;
     mApiVersion = 0;
-    mGrExtensions = GrVkExtensions();
+    mVulkanExtensions = skgpu::VulkanExtensions();
     mGrGetProc = nullptr;
     mIsProtected = false;
     mIsRealtimePriority = false;
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index af0489a..d0fe4d1 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -16,17 +16,14 @@
 
 #pragma once
 
-#include <include/gpu/vk/GrVkBackendContext.h>
-#include <include/gpu/vk/GrVkExtensions.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
+#include <include/gpu/vk/VulkanExtensions.h>
+#include <include/gpu/vk/VulkanTypes.h>
 
 #include <vulkan/vulkan.h>
 
 using namespace skgpu;
 
-namespace skgpu {
-struct VulkanBackendContext;
-} // namespace skgpu
-
 namespace android {
 namespace renderengine {
 namespace skia {
@@ -47,7 +44,8 @@
     bool takeOwnership();
     void teardown();
 
-    GrVkBackendContext getGaneshBackendContext();
+    // TODO(b/309785258) Combine these into one now that they are the same implementation.
+    VulkanBackendContext getGaneshBackendContext();
     VulkanBackendContext getGraphiteBackendContext();
     VkSemaphore createExportableSemaphore();
     VkSemaphore importSemaphoreFromSyncFd(int syncFd);
@@ -85,12 +83,12 @@
     VkQueue mQueue = VK_NULL_HANDLE;
     int mQueueIndex = 0;
     uint32_t mApiVersion = 0;
-    GrVkExtensions mGrExtensions;
+    skgpu::VulkanExtensions mVulkanExtensions;
     VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
     VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
     VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
     VkPhysicalDeviceFaultFeaturesEXT* mDeviceFaultFeatures = nullptr;
-    GrVkGetProc mGrGetProc = nullptr;
+    skgpu::VulkanGetProc mGrGetProc = nullptr;
     bool mIsProtected = false;
     bool mIsRealtimePriority = false;
 
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
index b2eae00..b121fe8 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -25,7 +25,7 @@
 #include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <include/gpu/gl/GrGLInterface.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 
 #include "../AutoBackendTexture.h"
 #include "GaneshBackendTexture.h"
@@ -56,10 +56,10 @@
 }
 
 std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
-        const GrVkBackendContext& grVkBackendContext,
+        const skgpu::VulkanBackendContext& vkBackendContext,
         GrContextOptions::PersistentCache& skSLCacheMonitor) {
     return std::make_unique<GaneshGpuContext>(
-            GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor)));
+            GrDirectContexts::MakeVulkan(vkBackendContext, ganeshOptions(skSLCacheMonitor)));
 }
 
 GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
index 282dfe7..9fa6fb8 100644
--- a/libs/renderengine/skia/compat/SkiaGpuContext.h
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -23,7 +23,6 @@
 #include <include/gpu/GrDirectContext.h>
 #include <include/gpu/gl/GrGLInterface.h>
 #include <include/gpu/graphite/Context.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
 #include "include/gpu/vk/VulkanBackendContext.h"
 
 #include "SkiaBackendTexture.h"
@@ -52,10 +51,10 @@
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     /**
-     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     * vkBackendContext must remain valid until after SkiaGpuContext is destroyed.
      */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
-            const GrVkBackendContext& grVkBackendContext,
+            const skgpu::VulkanBackendContext& vkBackendContext,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index 0eea187..0783714 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -46,6 +46,7 @@
         "libshaders",
         "libtonemap",
         "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
     ],
     header_libs: [
         "libtonemap_headers",
@@ -64,5 +65,6 @@
         "libui",
         "libutils",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
 }
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 4dcaff9..a8a9823 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -22,6 +22,7 @@
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <cutils/properties.h>
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
@@ -41,6 +42,14 @@
 #include "../skia/SkiaVkRenderEngine.h"
 #include "../threaded/RenderEngineThreaded.h"
 
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE) || \
+        COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(FORCE_COMPILE_GRAPHITE_RENDERENGINE)
+#define COMPILE_GRAPHITE_RENDERENGINE 1
+#else
+#define COMPILE_GRAPHITE_RENDERENGINE 0
+#endif
+
 constexpr int DEFAULT_DISPLAY_WIDTH = 128;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 256;
 constexpr int DEFAULT_DISPLAY_OFFSET = 64;
@@ -152,6 +161,8 @@
     }
 };
 
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COMPILE_GRAPHITE_RENDERENGINE
 class GraphiteVkRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "GraphiteVkRenderEngineFactory"; }
@@ -164,6 +175,7 @@
         return renderengine::RenderEngine::SkiaBackend::GRAPHITE;
     }
 };
+#endif
 
 class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
 public:
@@ -1497,10 +1509,15 @@
     expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
 }
 
+// TODO: b/341728634 - Clean up conditional compilation.
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
-                                         std::make_shared<GaneshVkRenderEngineFactory>(),
-                                         std::make_shared<GraphiteVkRenderEngineFactory>()));
+                                         std::make_shared<GaneshVkRenderEngineFactory>()
+#if COMPILE_GRAPHITE_RENDERENGINE
+                                                 ,
+                                         std::make_shared<GraphiteVkRenderEngineFactory>()
+#endif
+                                                 ));
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
     if (!GetParam()->apiSupported()) {
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 7fa47b4..659666d 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -63,6 +63,8 @@
         "libhardware",
         "libpermission",
         "android.companion.virtual.virtualdevice_aidl-cpp",
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
 
     static_libs: [
diff --git a/libs/sensor/SensorEventQueue.cpp b/libs/sensor/SensorEventQueue.cpp
index 4438d45..84852ea 100644
--- a/libs/sensor/SensorEventQueue.cpp
+++ b/libs/sensor/SensorEventQueue.cpp
@@ -15,31 +15,40 @@
  */
 
 #define LOG_TAG "Sensors"
-
-#include <sensor/SensorEventQueue.h>
-
-#include <algorithm>
-#include <sys/socket.h>
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <sensor/Sensor.h>
-#include <sensor/BitTube.h>
-#include <sensor/ISensorEventConnection.h>
+#define ATRACE_TAG ATRACE_TAG_SYSTEM_SERVER
 
 #include <android/sensor.h>
+#include <com_android_hardware_libsensor_flags.h>
+#include <cutils/trace.h>
 #include <hardware/sensors-base.h>
+#include <sensor/BitTube.h>
+#include <sensor/ISensorEventConnection.h>
+#include <sensor/Sensor.h>
+#include <sensor/SensorEventQueue.h>
+#include <sensor/SensorManager.h>
+#include <sys/socket.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
 
 using std::min;
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
 
-SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection)
-    : mSensorEventConnection(connection), mRecBuffer(nullptr), mAvailable(0), mConsumed(0),
-      mNumAcksToSend(0) {
+SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                                   SensorManager& sensorManager)
+      : mSensorEventConnection(connection),
+        mRecBuffer(nullptr),
+        mSensorManager(sensorManager),
+        mAvailable(0),
+        mConsumed(0),
+        mNumAcksToSend(0) {
     mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT];
 }
 
@@ -65,8 +74,8 @@
 
 ssize_t SensorEventQueue::read(ASensorEvent* events, size_t numEvents) {
     if (mAvailable == 0) {
-        ssize_t err = BitTube::recvObjects(mSensorChannel,
-                mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
+        ssize_t err =
+                BitTube::recvObjects(mSensorChannel, mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
         if (err < 0) {
             return err;
         }
@@ -75,6 +84,20 @@
     }
     size_t count = min(numEvents, mAvailable);
     memcpy(events, mRecBuffer + mConsumed, count * sizeof(ASensorEvent));
+
+    if (CC_UNLIKELY(ATRACE_ENABLED()) &&
+        libsensor_flags::sensor_event_queue_report_sensor_usage_in_tracing()) {
+        for (size_t i = 0; i < count; i++) {
+            std::optional<std::string_view> sensorName =
+                    mSensorManager.getSensorNameByHandle(events->sensor);
+            if (sensorName.has_value()) {
+                char buffer[UINT8_MAX];
+                std::snprintf(buffer, sizeof(buffer), "Sensor event from %s",
+                              sensorName.value().data());
+                ATRACE_INSTANT_FOR_TRACK(LOG_TAG, buffer);
+            }
+        }
+    }
     mAvailable -= count;
     mConsumed += count;
     return static_cast<ssize_t>(count);
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 9411e20..3ca6f0f 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -38,6 +38,7 @@
 #include <sensor/SensorEventQueue.h>
 
 #include <com_android_hardware_libsensor_flags.h>
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
@@ -78,6 +79,21 @@
     return DEVICE_ID_DEFAULT;
 }
 
+bool findSensorNameInList(int32_t handle, const Vector<Sensor>& sensorList,
+                          std::string* outString) {
+    for (auto& sensor : sensorList) {
+        if (sensor.getHandle() == handle) {
+            std::ostringstream oss;
+            oss << sensor.getStringType() << ":" << sensor.getName();
+            if (outString) {
+                *outString = std::move(oss.str());
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace
 
 Mutex SensorManager::sLock;
@@ -355,6 +371,25 @@
     return nullptr;
 }
 
+std::optional<std::string_view> SensorManager::getSensorNameByHandle(int32_t handle) {
+    std::lock_guard<std::mutex> lock(mSensorHandleToNameMutex);
+    auto iterator = mSensorHandleToName.find(handle);
+    if (iterator != mSensorHandleToName.end()) {
+        return iterator->second;
+    }
+
+    std::string sensorName;
+    if (!findSensorNameInList(handle, mSensors, &sensorName) &&
+        !findSensorNameInList(handle, mDynamicSensors, &sensorName)) {
+        ALOGW("Cannot find sensor with handle %d", handle);
+        return std::nullopt;
+    }
+
+    mSensorHandleToName[handle] = std::move(sensorName);
+
+    return mSensorHandleToName[handle];
+}
+
 sp<SensorEventQueue> SensorManager::createEventQueue(
     String8 packageName, int mode, String16 attributionTag) {
     sp<SensorEventQueue> queue;
@@ -368,7 +403,7 @@
             ALOGE("createEventQueue: connection is NULL.");
             return nullptr;
         }
-        queue = new SensorEventQueue(connection);
+        queue = new SensorEventQueue(connection, *this);
         break;
     }
     return queue;
diff --git a/libs/sensor/include/sensor/SensorEventQueue.h b/libs/sensor/include/sensor/SensorEventQueue.h
index 8c3fde0..0bcaadc 100644
--- a/libs/sensor/include/sensor/SensorEventQueue.h
+++ b/libs/sensor/include/sensor/SensorEventQueue.h
@@ -42,6 +42,7 @@
 // ----------------------------------------------------------------------------
 
 class ISensorEventConnection;
+class SensorManager;
 class Sensor;
 class Looper;
 
@@ -65,7 +66,8 @@
     // Default sensor sample period
     static constexpr int32_t SENSOR_DELAY_NORMAL = 200000;
 
-    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection);
+    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                              SensorManager& sensorManager);
     virtual ~SensorEventQueue();
     virtual void onFirstRef();
 
@@ -107,6 +109,7 @@
     mutable Mutex mLock;
     mutable sp<Looper> mLooper;
     ASensorEvent* mRecBuffer;
+    SensorManager& mSensorManager;
     size_t mAvailable;
     size_t mConsumed;
     uint32_t mNumAcksToSend;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 49f050a..8d7237d 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -17,22 +17,20 @@
 #ifndef ANDROID_GUI_SENSOR_MANAGER_H
 #define ANDROID_GUI_SENSOR_MANAGER_H
 
-#include <map>
-#include <unordered_map>
-
-#include <stdint.h>
-#include <sys/types.h>
-
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-
+#include <sensor/SensorEventQueue.h>
+#include <stdint.h>
+#include <sys/types.h>
 #include <utils/Errors.h>
+#include <utils/String8.h>
 #include <utils/StrongPointer.h>
 #include <utils/Vector.h>
-#include <utils/String8.h>
 
-#include <sensor/SensorEventQueue.h>
+#include <map>
+#include <string>
+#include <unordered_map>
 
 // ----------------------------------------------------------------------------
 // Concrete types for the NDK
@@ -66,6 +64,7 @@
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
+    std::optional<std::string_view> getSensorNameByHandle(int32_t handle);
     bool isReplayDataInjectionEnabled();
     bool isHalBypassReplayDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
@@ -97,6 +96,9 @@
     const String16 mOpPackageName;
     const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
+
+    std::mutex mSensorHandleToNameMutex;
+    std::unordered_map<int32_t, std::string> mSensorHandleToName;
     int32_t mDirectConnectionHandle;
 };
 
diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig
index c511f4a..cbf3055 100644
--- a/libs/sensor/libsensor_flags.aconfig
+++ b/libs/sensor/libsensor_flags.aconfig
@@ -8,3 +8,10 @@
   bug: "322228259"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "sensor_event_queue_report_sensor_usage_in_tracing"
+  namespace: "sensors"
+  description: "When this flag is enabled, sensor event queue will report sensor usage when system trace is enabled."
+  bug: "333132224"
+}
\ No newline at end of file
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index f14a5cf..c9ec036 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -91,8 +91,7 @@
         }
 
         void* so = nullptr;
-        // TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
-        if API_LEVEL_AT_LEAST(__ANDROID_API_FUTURE__, 202404) {
+        if API_LEVEL_AT_LEAST (__ANDROID_API_V__, 202404) {
             so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
                                                             RTLD_LOCAL | RTLD_NOW);
         } else {
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 02453ef..00dd6ba 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -21,6 +21,7 @@
 #if defined(__ANDROID__)
 #include <gui/SurfaceComposerClient.h>
 #endif
+#include <input/Keyboard.h>
 #include <input/PrintTools.h>
 #include <unordered_set>
 
@@ -30,10 +31,6 @@
 
 namespace android {
 
-namespace input_flags = com::android::input::flags;
-static const bool HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS =
-        input_flags::hide_pointer_indicators_for_secure_windows();
-
 namespace {
 
 bool isFromMouse(const NotifyMotionArgs& args) {
@@ -106,8 +103,31 @@
 
 // --- PointerChoreographer ---
 
-PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
+PointerChoreographer::PointerChoreographer(InputListenerInterface& inputListener,
                                            PointerChoreographerPolicyInterface& policy)
+      : PointerChoreographer(
+                inputListener, policy,
+                [](const sp<android::gui::WindowInfosListener>& listener) {
+                    auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+                                                      std::vector<android::gui::DisplayInfo>{});
+#if defined(__ANDROID__)
+                    SurfaceComposerClient::getDefault()->addWindowInfosListener(listener,
+                                                                                &initialInfo);
+#endif
+                    return initialInfo.first;
+                },
+                [](const sp<android::gui::WindowInfosListener>& listener) {
+#if defined(__ANDROID__)
+                    SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
+#endif
+                }) {
+}
+
+PointerChoreographer::PointerChoreographer(
+        android::InputListenerInterface& listener,
+        android::PointerChoreographerPolicyInterface& policy,
+        const android::PointerChoreographer::WindowListenerRegisterConsumer& registerListener,
+        const android::PointerChoreographer::WindowListenerUnregisterConsumer& unregisterListener)
       : mTouchControllerConstructor([this]() {
             return mPolicy.createPointerController(
                     PointerControllerInterface::ControllerType::TOUCH);
@@ -117,7 +137,10 @@
         mDefaultMouseDisplayId(ui::LogicalDisplayId::DEFAULT),
         mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
         mShowTouchesEnabled(false),
-        mStylusPointerIconEnabled(false) {}
+        mStylusPointerIconEnabled(false),
+        mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
+        mRegisterListener(registerListener),
+        mUnregisterListener(unregisterListener) {}
 
 PointerChoreographer::~PointerChoreographer() {
     std::scoped_lock _l(mLock);
@@ -125,6 +148,7 @@
         return;
     }
     mWindowInfoListener->onPointerChoreographerDestroyed();
+    mUnregisterListener(mWindowInfoListener);
 }
 
 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
@@ -146,6 +170,7 @@
 }
 
 void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
+    fadeMouseCursorOnKeyPress(args);
     mNextListener.notify(args);
 }
 
@@ -155,6 +180,32 @@
     mNextListener.notify(newArgs);
 }
 
+void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArgs& args) {
+    if (args.action == AKEY_EVENT_ACTION_UP || isMetaKey(args.keyCode)) {
+        return;
+    }
+    // Meta state for these keys is ignored for dismissing cursor while typing
+    constexpr static int32_t ALLOW_FADING_META_STATE_MASK = AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON |
+            AMETA_SCROLL_LOCK_ON | AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON | AMETA_SHIFT_ON;
+    if (args.metaState & ~ALLOW_FADING_META_STATE_MASK) {
+        // Do not fade if any other meta state is active
+        return;
+    }
+    if (!mPolicy.isInputMethodConnectionActive()) {
+        return;
+    }
+
+    std::scoped_lock _l(mLock);
+    ui::LogicalDisplayId targetDisplay = args.displayId;
+    if (targetDisplay == ui::LogicalDisplayId::INVALID) {
+        targetDisplay = mCurrentFocusedDisplay;
+    }
+    auto it = mMousePointersByDisplay.find(targetDisplay);
+    if (it != mMousePointersByDisplay.end()) {
+        it->second->fade(PointerControllerInterface::Transition::GRADUAL);
+    }
+}
+
 NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
     std::scoped_lock _l(mLock);
 
@@ -391,7 +442,7 @@
 }
 
 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
-    if (!HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS) {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
         return;
     }
     bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -399,18 +450,10 @@
 
     if (requireListener && mWindowInfoListener == nullptr) {
         mWindowInfoListener = sp<PointerChoreographerDisplayInfoListener>::make(this);
-        auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
-                                          std::vector<android::gui::DisplayInfo>{});
-#if defined(__ANDROID__)
-        SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener,
-                                                                    &initialInfo);
-#endif
-        mWindowInfoListener->setInitialDisplayInfos(initialInfo.first);
+        mWindowInfoListener->setInitialDisplayInfos(mRegisterListener(mWindowInfoListener));
         onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays());
     } else if (!requireListener && mWindowInfoListener != nullptr) {
-#if defined(__ANDROID__)
-        SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener);
-#endif
+        mUnregisterListener(mWindowInfoListener);
         mWindowInfoListener = nullptr;
     } else if (requireListener && mWindowInfoListener != nullptr) {
         // controller may have been added to an existing privacy sensitive display, we need to
@@ -792,6 +835,11 @@
     }
 }
 
+void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) {
+    std::scoped_lock lock(mLock);
+    mCurrentFocusedDisplay = displayId;
+}
+
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
         ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 11c5a0c..aaf1e3e 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -76,6 +76,11 @@
     virtual void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) = 0;
 
     /**
+     * Used by Dispatcher to notify changes in the current focused display.
+     */
+    virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
+
+    /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
     virtual void dump(std::string& dump) = 0;
@@ -97,6 +102,7 @@
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
                         ui::LogicalDisplayId displayId, DeviceId deviceId) override;
     void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
+    void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -108,10 +114,6 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
-    // Public because it's also used by tests to simulate the WindowInfosListener callback
-    void onPrivacySensitiveDisplaysChanged(
-            const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays);
-
     void dump(std::string& dump) override;
 
 private:
@@ -128,6 +130,7 @@
     InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
     bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock);
 
+    void fadeMouseCursorOnKeyPress(const NotifyKeyArgs& args);
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
@@ -139,6 +142,8 @@
     void onPrivacySensitiveDisplaysChangedLocked(
             const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
             REQUIRES(mLock);
+    void onPrivacySensitiveDisplaysChanged(
+            const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays);
 
     /* This listener keeps tracks of visible privacy sensitive displays and updates the
      * choreographer if there are any changes.
@@ -194,6 +199,21 @@
     bool mShowTouchesEnabled GUARDED_BY(mLock);
     bool mStylusPointerIconEnabled GUARDED_BY(mLock);
     std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
+    ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(mLock);
+
+protected:
+    using WindowListenerRegisterConsumer = std::function<std::vector<gui::WindowInfo>(
+            const sp<android::gui::WindowInfosListener>&)>;
+    using WindowListenerUnregisterConsumer =
+            std::function<void(const sp<android::gui::WindowInfosListener>&)>;
+    explicit PointerChoreographer(InputListenerInterface& listener,
+                                  PointerChoreographerPolicyInterface&,
+                                  const WindowListenerRegisterConsumer& registerListener,
+                                  const WindowListenerUnregisterConsumer& unregisterListener);
+
+private:
+    const WindowListenerRegisterConsumer mRegisterListener;
+    const WindowListenerUnregisterConsumer mUnregisterListener;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 29aa3c3..1a0ec48 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -56,7 +56,9 @@
 
 cc_defaults {
     name: "libinputdispatcher_defaults",
-    srcs: [":libinputdispatcher_sources"],
+    srcs: [
+        ":libinputdispatcher_sources",
+    ],
     shared_libs: [
         "libbase",
         "libbinder",
@@ -78,6 +80,7 @@
         "libattestation",
         "libgui_window_info_static",
         "libperfetto_client_experimental",
+        "perfetto_winscope_extensions_zero",
     ],
     target: {
         android: {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index dae2b61..a76271d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -444,10 +444,12 @@
             newCoords.copyFrom(motionEntry.pointerCoords[i]);
             // First, apply the current pointer's transform to update the coordinates into
             // window space.
-            newCoords.transform(currTransform);
+            MotionEvent::calculateTransformedCoordsInPlace(newCoords, motionEntry.source,
+                                                           motionEntry.flags, currTransform);
             // Next, apply the inverse transform of the normalized coordinates so the
             // current coordinates are transformed into the normalized coordinate space.
-            newCoords.transform(inverseTransform);
+            MotionEvent::calculateTransformedCoordsInPlace(newCoords, motionEntry.source,
+                                                           motionEntry.flags, inverseTransform);
         }
     }
 
@@ -877,7 +879,7 @@
 class ScopedSyntheticEventTracer {
 public:
     ScopedSyntheticEventTracer(std::unique_ptr<trace::InputTracerInterface>& tracer)
-          : mTracer(tracer) {
+          : mTracer(tracer), mProcessingTimestamp(now()) {
         if (mTracer) {
             mEventTracker = mTracer->createTrackerForSyntheticEvent();
         }
@@ -885,7 +887,7 @@
 
     ~ScopedSyntheticEventTracer() {
         if (mTracer) {
-            mTracer->eventProcessingComplete(*mEventTracker);
+            mTracer->eventProcessingComplete(*mEventTracker, mProcessingTimestamp);
         }
     }
 
@@ -894,8 +896,9 @@
     }
 
 private:
-    std::unique_ptr<trace::InputTracerInterface>& mTracer;
+    const std::unique_ptr<trace::InputTracerInterface>& mTracer;
     std::unique_ptr<trace::EventTrackerInterface> mEventTracker;
+    const nsecs_t mProcessingTimestamp;
 };
 
 } // namespace
@@ -1261,7 +1264,7 @@
 
         if (mTracer) {
             if (auto& traceTracker = getTraceTracker(*mPendingEvent); traceTracker != nullptr) {
-                mTracer->eventProcessingComplete(*traceTracker);
+                mTracer->eventProcessingComplete(*traceTracker, currentTime);
             }
         }
 
@@ -1894,8 +1897,6 @@
                 doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
             };
             postCommandLocked(std::move(command));
-            // Poke user activity for keys not passed to user
-            pokeUserActivityLocked(*entry);
             return false; // wait for the command to run
         } else {
             entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
@@ -1912,8 +1913,12 @@
                            *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                              : InputEventInjectionResult::FAILED);
         mReporter->reportDroppedKey(entry->id);
-        // Poke user activity for undispatched keys
-        pokeUserActivityLocked(*entry);
+        // Poke user activity for consumed keys, as it may have not been reported due to
+        // the focused window requesting user activity to be disabled
+        if (*dropReason == DropReason::POLICY &&
+            mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+            pokeUserActivityLocked(*entry);
+        }
         return true;
     }
 
@@ -3313,22 +3318,16 @@
             if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
                 return;
             }
-            // If the key code is unknown, we don't consider it user activity
-            if (keyEntry.keyCode == AKEYCODE_UNKNOWN) {
-                return;
-            }
             // Don't inhibit events that were intercepted or are not passed to
             // the apps, like system shortcuts
             if (windowDisablingUserActivityInfo != nullptr &&
-                keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP &&
-                keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER) {
+                keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP) {
                 if (DEBUG_DISPATCH_CYCLE) {
                     ALOGD("Not poking user activity: disabled by window '%s'.",
                           windowDisablingUserActivityInfo->name.c_str());
                 }
                 return;
             }
-
             break;
         }
         default: {
@@ -4885,10 +4884,11 @@
 
             mLock.lock();
 
-            if (policyFlags & POLICY_FLAG_FILTERED) {
-                // The events from InputFilter impersonate real hardware devices. Check these
-                // events for consistency and print an error. An inconsistent event sent from
-                // InputFilter could cause a crash in the later stages of dispatching pipeline.
+            {
+                // Verify all injected streams, whether the injection is coming from apps or from
+                // input filter. Print an error if the stream becomes inconsistent with this event.
+                // An inconsistent injected event sent could cause a crash in the later stages of
+                // dispatching pipeline.
                 auto [it, _] =
                         mInputFilterVerifiersByDisplay.try_emplace(displayId,
                                                                    std::string("Injection on ") +
@@ -5132,8 +5132,8 @@
     }
     for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
         entry.pointerCoords[i] =
-                MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
-                                                        entry.pointerCoords[i]);
+                MotionEvent::calculateTransformedCoords(entry.source, entry.flags,
+                                                        transformToDisplay, entry.pointerCoords[i]);
     }
 }
 
@@ -5527,6 +5527,17 @@
                 synthesizeCancelationEventsForWindowLocked(windowHandle, options);
             }
             mFocusedDisplayId = displayId;
+            // Enqueue a command to run outside the lock to tell the policy that the focused display
+            // changed.
+            auto command = [this]() REQUIRES(mLock) {
+                scoped_unlock unlock(mLock);
+                mPolicy.notifyFocusedDisplayChanged(mFocusedDisplayId);
+            };
+            postCommandLocked(std::move(command));
+
+            // Only a window on the focused display can have Pointer Capture, so disable the active
+            // Pointer Capture session if there is one, since the focused display changed.
+            disablePointerCaptureForcedLocked();
 
             // Find new focused window and validate
             sp<IBinder> newFocusedWindowToken = mFocusResolver.getFocusedWindowToken(displayId);
@@ -6388,9 +6399,8 @@
         }
 
         if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) {
-            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*(dispatchEntry.eventEntry));
             fallbackKeyEntry =
-                    afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
+                    afterKeyEventLockedInterruptable(connection, &dispatchEntry, handled);
         }
     } // End critical section: The -LockedInterruptable methods may have released the lock.
 
@@ -6614,8 +6624,17 @@
 }
 
 std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptable(
-        const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
-        const KeyEntry& keyEntry, bool handled) {
+        const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry, bool handled) {
+    // The dispatchEntry is currently valid, but it might point to a deleted object after we release
+    // the lock. For simplicity, make copies of the data of interest here and assume that
+    // 'dispatchEntry' is not valid after this section.
+    // Hold a strong reference to the EventEntry to ensure it's valid for the duration of this
+    // function, even if the DispatchEntry gets destroyed and releases its share of the ownership.
+    std::shared_ptr<const EventEntry> eventEntry = dispatchEntry->eventEntry;
+    const bool hasForegroundTarget = dispatchEntry->hasForegroundTarget();
+    const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*(eventEntry));
+    // To prevent misuse, ensure dispatchEntry is no longer valid.
+    dispatchEntry = nullptr;
     if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
         if (!handled) {
             // Report the key as unhandled, since the fallback was not handled.
@@ -6632,7 +6651,7 @@
         connection->inputState.removeFallbackKey(originalKeyCode);
     }
 
-    if (handled || !dispatchEntry.hasForegroundTarget()) {
+    if (handled || !hasForegroundTarget) {
         // If the application handles the original key for which we previously
         // generated a fallback or if the window is not a foreground window,
         // then cancel the associated fallback key, if any.
@@ -6920,17 +6939,17 @@
         enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason);
     }
 
-    // If a window has pointer capture, then it must have focus. We need to ensure that this
-    // contract is upheld when pointer capture is being disabled due to a loss of window focus.
-    // If the window loses focus before it loses pointer capture, then the window can be in a state
-    // where it has pointer capture but not focus, violating the contract. Therefore we must
-    // dispatch the pointer capture event before the focus event. Since focus events are added to
-    // the front of the queue (above), we add the pointer capture event to the front of the queue
-    // after the focus events are added. This ensures the pointer capture event ends up at the
-    // front.
-    disablePointerCaptureForcedLocked();
-
     if (mFocusedDisplayId == changes.displayId) {
+        // If a window has pointer capture, then it must have focus and must be on the top-focused
+        // display. We need to ensure that this contract is upheld when pointer capture is being
+        // disabled due to a loss of window focus. If the window loses focus before it loses pointer
+        // capture, then the window can be in a state where it has pointer capture but not focus,
+        // violating the contract. Therefore we must dispatch the pointer capture event before the
+        // focus event. Since focus events are added to the front of the queue (above), we add the
+        // pointer capture event to the front of the queue after the focus events are added. This
+        // ensures the pointer capture event ends up at the front.
+        disablePointerCaptureForcedLocked();
+
         sendFocusChangedCommandLocked(changes.oldFocus, changes.newFocus);
     }
 }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 6240e7f..e2fc7a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -687,8 +687,8 @@
     std::map<ui::LogicalDisplayId /*displayId*/, InputVerifier> mVerifiersByDisplay;
     // Returns a fallback KeyEntry that should be sent to the connection, if required.
     std::unique_ptr<const KeyEntry> afterKeyEventLockedInterruptable(
-            const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
-            const KeyEntry& keyEntry, bool handled) REQUIRES(mLock);
+            const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+            bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
     std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId /*displayId*/>
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 46e7e8b..dfbe02f 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -499,67 +499,53 @@
         nsecs_t currentTime) {
     std::vector<std::unique_ptr<MotionEntry>> events;
     std::vector<uint32_t> canceledPointerIndices;
-    std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
-    std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
+
     for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) {
         uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
-        pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx];
-        pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx];
         if (pointerIds.test(pointerId)) {
             canceledPointerIndices.push_back(pointerIdx);
         }
     }
 
     if (canceledPointerIndices.size() == memento.getPointerCount()) {
-        const int32_t action =
-                memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
-        int32_t flags = memento.flags;
-        if (action == AMOTION_EVENT_ACTION_CANCEL) {
-            flags |= AMOTION_EVENT_FLAG_CANCELED;
+        // We are cancelling all pointers.
+        events.emplace_back(createCancelEntryForMemento(memento, currentTime));
+        return events;
+    }
+
+    // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
+    // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
+    // previously canceled pointers from PointerProperties and PointerCoords, and update
+    // pointerCount appropriately. For convenience, sort the canceled pointer indices in
+    // descending order so that we can just slide the remaining pointers to the beginning of
+    // the array when a pointer is canceled.
+    std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
+              std::greater<uint32_t>());
+
+    std::vector<PointerProperties> pointerProperties = memento.pointerProperties;
+    std::vector<PointerCoords> pointerCoords = memento.pointerCoords;
+    for (const uint32_t pointerIdx : canceledPointerIndices) {
+        if (pointerProperties.size() <= 1) {
+            LOG(FATAL) << "Unexpected code path for canceling all pointers!";
         }
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
         events.push_back(
                 std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
                                               currentTime, memento.deviceId, memento.source,
                                               memento.displayId, memento.policyFlags, action,
-                                              /*actionButton=*/0, flags, AMETA_NONE,
-                                              /*buttonState=*/0, MotionClassification::NONE,
+                                              /*actionButton=*/0,
+                                              memento.flags | AMOTION_EVENT_FLAG_CANCELED,
+                                              AMETA_NONE, /*buttonState=*/0,
+                                              MotionClassification::NONE,
                                               AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                               memento.yPrecision, memento.xCursorPosition,
                                               memento.yCursorPosition, memento.downTime,
-                                              memento.pointerProperties, memento.pointerCoords));
-    } else {
-        // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
-        // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
-        // previously canceled pointers from PointerProperties and PointerCoords, and update
-        // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we
-        // can just slide the remaining pointers to the beginning of the array when a pointer is
-        // canceled.
-        std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
-                  std::greater<uint32_t>());
+                                              pointerProperties, pointerCoords));
 
-        uint32_t pointerCount = memento.getPointerCount();
-        for (const uint32_t pointerIdx : canceledPointerIndices) {
-            const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
-                                                     : AMOTION_EVENT_ACTION_POINTER_UP |
-                            (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-            events.push_back(
-                    std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
-                                                  currentTime, memento.deviceId, memento.source,
-                                                  memento.displayId, memento.policyFlags, action,
-                                                  /*actionButton=*/0,
-                                                  memento.flags | AMOTION_EVENT_FLAG_CANCELED,
-                                                  AMETA_NONE, /*buttonState=*/0,
-                                                  MotionClassification::NONE,
-                                                  AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
-                                                  memento.yPrecision, memento.xCursorPosition,
-                                                  memento.yCursorPosition, memento.downTime,
-                                                  pointerProperties, pointerCoords));
-
-            // Cleanup pointer information
-            pointerProperties.erase(pointerProperties.begin() + pointerIdx);
-            pointerCoords.erase(pointerCoords.begin() + pointerIdx);
-            pointerCount--;
-        }
+        // Cleanup pointer information
+        pointerProperties.erase(pointerProperties.begin() + pointerIdx);
+        pointerCoords.erase(pointerCoords.begin() + pointerIdx);
     }
     return events;
 }
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 0f03620..65fb76d 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -76,6 +76,11 @@
                                       InputDeviceSensorAccuracy accuracy) = 0;
     virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0;
 
+    /*
+     * Notifies the system that focused display has changed.
+     */
+    virtual void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) = 0;
+
     /* Filters an input event.
      * Return true to dispatch the event unmodified, false to consume the event.
      * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index 2d7554c..0b17507 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -123,7 +123,8 @@
 
             const auto& coords = motion->pointerCoords[i];
             const auto coordsInWindow =
-                    MotionEvent::calculateTransformedCoords(motion->source, args.transform, coords);
+                    MotionEvent::calculateTransformedCoords(motion->source, motion->flags,
+                                                            args.transform, coords);
             auto bits = BitSet64(coords.bits);
             for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
                 const uint32_t axis = bits.clearFirstMarkedBit();
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index a1a87af..5d2b854 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -145,7 +145,8 @@
     eventState->metadata.isSecure |= targetInfo.isSecureWindow;
 }
 
-void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
+void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie,
+                                          nsecs_t processingTimestamp) {
     if (isDerivedCookie(cookie)) {
         LOG(FATAL) << "Event processing cannot be set from a derived cookie.";
     }
@@ -154,7 +155,7 @@
         LOG(FATAL) << "Traced event was already logged. "
                       "eventProcessingComplete() was likely called more than once.";
     }
-    eventState->onEventProcessingComplete();
+    eventState->onEventProcessingComplete(processingTimestamp);
 }
 
 std::unique_ptr<EventTrackerInterface> InputTracer::traceDerivedEvent(
@@ -242,7 +243,8 @@
 
 // --- InputTracer::EventState ---
 
-void InputTracer::EventState::onEventProcessingComplete() {
+void InputTracer::EventState::onEventProcessingComplete(nsecs_t processingTimestamp) {
+    metadata.processingTimestamp = processingTimestamp;
     metadata.isImeConnectionActive = tracer.mIsImeConnectionActive;
 
     // Write all of the events known so far to the trace.
@@ -277,7 +279,7 @@
     // We should never end up here in normal operation. However, in tests, it's possible that we
     // stop and destroy InputDispatcher without waiting for it to finish processing events, at
     // which point an event (and thus its EventState) may be destroyed before processing finishes.
-    onEventProcessingComplete();
+    onEventProcessingComplete(systemTime(CLOCK_MONOTONIC));
 }
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index ab175be..cb525a4 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -44,7 +44,8 @@
     std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) override;
     std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() override;
     void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override;
-    void eventProcessingComplete(const EventTrackerInterface&) override;
+    void eventProcessingComplete(const EventTrackerInterface&,
+                                 nsecs_t processingTimestamp) override;
     std::unique_ptr<EventTrackerInterface> traceDerivedEvent(const EventEntry&,
                                                              const EventTrackerInterface&) override;
     void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override;
@@ -61,7 +62,7 @@
         explicit inline EventState(InputTracer& tracer) : tracer(tracer){};
         ~EventState();
 
-        void onEventProcessingComplete();
+        void onEventProcessingComplete(nsecs_t processingTimestamp);
 
         InputTracer& tracer;
         std::vector<const TracedEvent> events;
diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
index af6eefb..f5e4e59 100644
--- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
@@ -81,7 +81,8 @@
      * outside of our control, such as how long apps take to respond, so we don't want to depend on
      * that.
      */
-    virtual void eventProcessingComplete(const EventTrackerInterface&) = 0;
+    virtual void eventProcessingComplete(const EventTrackerInterface&,
+                                         nsecs_t processingTimestamp) = 0;
 
     /**
      * Trace an input event that is derived from another event. This is used in cases where an event
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index 25099c3..761d619 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -99,6 +99,8 @@
     std::set<gui::Uid> targets;
     // True if the there was an active input method connection while this event was processed.
     bool isImeConnectionActive;
+    // The timestamp for when the dispatching decisions were made for the event by the system.
+    nsecs_t processingTimestamp;
 };
 
 /** Additional information about an input event being dispatched to a window. */
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 9b9633a..77b5c2e 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -23,6 +23,8 @@
 #include <android-base/logging.h>
 #include <binder/IServiceManager.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions_impl.pbzero.h>
 #include <private/android_filesystem_config.h>
 #include <utils/String16.h>
 
@@ -229,7 +231,11 @@
         }
         const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
-        auto* inputEvent = tracePacket->set_android_input_event();
+        tracePacket->set_timestamp(metadata.processingTimestamp);
+        tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        auto* winscopeExtensions = static_cast<perfetto::protos::pbzero::WinscopeExtensionsImpl*>(
+                tracePacket->set_winscope_extensions());
+        auto* inputEvent = winscopeExtensions->set_android_input_event();
         auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted()
                                           : inputEvent->set_dispatcher_motion_event();
         AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
@@ -253,7 +259,11 @@
         }
         const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
-        auto* inputEvent = tracePacket->set_android_input_event();
+        tracePacket->set_timestamp(metadata.processingTimestamp);
+        tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        auto* winscopeExtensions = static_cast<perfetto::protos::pbzero::WinscopeExtensionsImpl*>(
+                tracePacket->set_winscope_extensions());
+        auto* inputEvent = winscopeExtensions->set_android_input_event();
         auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted()
                                        : inputEvent->set_dispatcher_key_event();
         AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
@@ -277,7 +287,11 @@
         }
         const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
-        auto* inputEvent = tracePacket->set_android_input_event();
+        tracePacket->set_timestamp(dispatchArgs.deliveryTime);
+        tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        auto* winscopeExtensions = static_cast<perfetto::protos::pbzero::WinscopeExtensionsImpl*>(
+                tracePacket->set_winscope_extensions());
+        auto* inputEvent = winscopeExtensions->set_android_input_event();
         auto* dispatchEvent = isRedacted
                 ? inputEvent->set_dispatcher_window_dispatch_event_redacted()
                 : inputEvent->set_dispatcher_window_dispatch_event();
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index cae638f..5b94d57 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -21,6 +21,7 @@
 #include <attestation/HmacKeyManager.h>
 #include <input/Input.h>
 #include <input/InputEventBuilders.h>
+#include <input/Keyboard.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
 #include <vector>
@@ -206,6 +207,12 @@
         return *this;
     }
 
+    KeyArgsBuilder& metaState(int32_t metaState) {
+        mMetaState |= metaState;
+        mMetaState = normalizeMetaState(/*oldMetaState=*/mMetaState);
+        return *this;
+    }
+
     NotifyKeyArgs build() const {
         return {mEventId,
                 mEventTime,
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index f6dc109..7a85c12 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -55,6 +55,9 @@
      */
     virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
                                                const FloatPoint& position) = 0;
+
+    /* Returns true if any InputConnection is currently active. */
+    virtual bool isInputMethodConnectionActive() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index ba586d7..e76b648 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -91,7 +91,6 @@
         "libutils",
     ],
     static_libs: [
-        "libc++fs",
         "libchrome-gestures",
         "libui-types",
     ],
@@ -156,7 +155,6 @@
         },
     },
     static_libs: [
-        "libc++fs",
         "libchrome-gestures",
     ],
 }
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index fe70a51..65583e9 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -998,26 +998,23 @@
     return *device->configuration;
 }
 
-status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                       RawAbsoluteAxisInfo* outAxisInfo) const {
-    outAxisInfo->clear();
+std::optional<RawAbsoluteAxisInfo> EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
     // fd, because the info is populated once when the device is first opened, and it doesn't change
     // throughout the device lifecycle.
     auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outAxisInfo = it->second.info;
-    return OK;
+    return it->second.info;
 }
 
 bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -1130,22 +1127,20 @@
     return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
 }
 
-status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
-    *outValue = 0;
+std::optional<int32_t> EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr || !device->hasValidFd()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     const auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outValue = it->second.value;
-    return OK;
+    return it->second.value;
 }
 
 base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index b807b27..2daf195 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -237,6 +237,12 @@
     mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
     mHasMic = mClasses.test(InputDeviceClass::MIC);
 
+    // Update keyboard type
+    if (mClasses.test(InputDeviceClass::KEYBOARD)) {
+        mContext->getKeyboardClassifier().notifyKeyboardChanged(mId, mIdentifier, mClasses.get());
+        mKeyboardType = mContext->getKeyboardClassifier().getKeyboardType(mId);
+    }
+
     using Change = InputReaderConfiguration::Change;
 
     if (!changes.any() || !isIgnored()) {
@@ -403,7 +409,7 @@
             mDropUntilNextSync = true;
         } else {
             for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
-                out += mapper.process(rawEvent);
+                out += mapper.process(*rawEvent);
             });
         }
         --count;
@@ -445,6 +451,7 @@
                              mHasMic,
                              getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID),
                              {mShouldSmoothScroll}, isEnabled());
+    outDeviceInfo.setKeyboardType(static_cast<int32_t>(mKeyboardType));
 
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
@@ -517,13 +524,9 @@
 
     // Keyboard-like devices.
     uint32_t keyboardSource = 0;
-    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
     if (classes.test(InputDeviceClass::KEYBOARD)) {
         keyboardSource |= AINPUT_SOURCE_KEYBOARD;
     }
-    if (classes.test(InputDeviceClass::ALPHAKEY)) {
-        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
-    }
     if (classes.test(InputDeviceClass::DPAD)) {
         keyboardSource |= AINPUT_SOURCE_DPAD;
     }
@@ -532,8 +535,8 @@
     }
 
     if (keyboardSource != 0) {
-        mappers.push_back(createInputMapper<KeyboardInputMapper>(contextPtr, readerConfig,
-                                                                 keyboardSource, keyboardType));
+        mappers.push_back(
+                createInputMapper<KeyboardInputMapper>(contextPtr, readerConfig, keyboardSource));
     }
 
     // Cursor-like devices.
@@ -730,6 +733,13 @@
     return mController ? std::make_optional(mController->getEventHubId()) : std::nullopt;
 }
 
+void InputDevice::setKeyboardType(KeyboardType keyboardType) {
+    if (mKeyboardType != keyboardType) {
+        mKeyboardType = keyboardType;
+        bumpGeneration();
+    }
+}
+
 InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId)
       : mDevice(device),
         mContext(device.getContext()),
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index b9523ef..ab13ad4 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -102,6 +102,7 @@
         mEventHub(eventHub),
         mPolicy(policy),
         mNextListener(listener),
+        mKeyboardClassifier(std::make_unique<KeyboardClassifier>()),
         mGlobalMetaState(AMETA_NONE),
         mLedMetaState(AMETA_NONE),
         mGeneration(1),
@@ -1076,4 +1077,8 @@
     return mIdGenerator.nextId();
 }
 
+KeyboardClassifier& InputReader::ContextImpl::getKeyboardClassifier() {
+    return *mReader->mKeyboardClassifier;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 27b9d23..49ad8b5 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -418,7 +418,11 @@
         }
         rawInfos.insert_or_assign(rawId, rawInfo.value());
         // Check if this is a group LEDs for player ID
-        std::regex lightPattern("([a-z]+)([0-9]+)");
+        // The name for the light has already been parsed and is the `function`
+        // value; for player ID lights the function is expected to be `player-#`.
+        // However, the Sony driver will use `sony#` instead on SIXAXIS
+        // gamepads.
+        std::regex lightPattern("(player|sony)-?([0-9]+)");
         std::smatch results;
         if (std::regex_match(rawInfo->name, results, lightPattern)) {
             std::string commonName = results[1].str();
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 39d2f5b..2a43466 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -21,6 +21,7 @@
 #include <filesystem>
 #include <functional>
 #include <map>
+#include <optional>
 #include <ostream>
 #include <string>
 #include <unordered_map>
@@ -85,64 +86,67 @@
 
 /*
  * Input device classes.
+ *
+ * These classes are duplicated in rust side here: /frameworks/native/libs/input/rust/input.rs.
+ * If any new classes are added, we need to add them in rust input side too.
  */
 enum class InputDeviceClass : uint32_t {
     /* The input device is a keyboard or has buttons. */
-    KEYBOARD = 0x00000001,
+    KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD,
 
     /* The input device is an alpha-numeric keyboard (not just a dial pad). */
-    ALPHAKEY = 0x00000002,
+    ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY,
 
     /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
-    TOUCH = 0x00000004,
+    TOUCH = android::os::IInputConstants::DEVICE_CLASS_TOUCH,
 
     /* The input device is a cursor device such as a trackball or mouse. */
-    CURSOR = 0x00000008,
+    CURSOR = android::os::IInputConstants::DEVICE_CLASS_CURSOR,
 
     /* The input device is a multi-touch touchscreen or touchpad. */
-    TOUCH_MT = 0x00000010,
+    TOUCH_MT = android::os::IInputConstants::DEVICE_CLASS_TOUCH_MT,
 
     /* The input device is a directional pad (implies keyboard, has DPAD keys). */
-    DPAD = 0x00000020,
+    DPAD = android::os::IInputConstants::DEVICE_CLASS_DPAD,
 
     /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
-    GAMEPAD = 0x00000040,
+    GAMEPAD = android::os::IInputConstants::DEVICE_CLASS_GAMEPAD,
 
     /* The input device has switches. */
-    SWITCH = 0x00000080,
+    SWITCH = android::os::IInputConstants::DEVICE_CLASS_SWITCH,
 
     /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
-    JOYSTICK = 0x00000100,
+    JOYSTICK = android::os::IInputConstants::DEVICE_CLASS_JOYSTICK,
 
     /* The input device has a vibrator (supports FF_RUMBLE). */
-    VIBRATOR = 0x00000200,
+    VIBRATOR = android::os::IInputConstants::DEVICE_CLASS_VIBRATOR,
 
     /* The input device has a microphone. */
-    MIC = 0x00000400,
+    MIC = android::os::IInputConstants::DEVICE_CLASS_MIC,
 
     /* The input device is an external stylus (has data we want to fuse with touch data). */
-    EXTERNAL_STYLUS = 0x00000800,
+    EXTERNAL_STYLUS = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS,
 
     /* The input device has a rotary encoder */
-    ROTARY_ENCODER = 0x00001000,
+    ROTARY_ENCODER = android::os::IInputConstants::DEVICE_CLASS_ROTARY_ENCODER,
 
     /* The input device has a sensor like accelerometer, gyro, etc */
-    SENSOR = 0x00002000,
+    SENSOR = android::os::IInputConstants::DEVICE_CLASS_SENSOR,
 
     /* The input device has a battery */
-    BATTERY = 0x00004000,
+    BATTERY = android::os::IInputConstants::DEVICE_CLASS_BATTERY,
 
     /* The input device has sysfs controllable lights */
-    LIGHT = 0x00008000,
+    LIGHT = android::os::IInputConstants::DEVICE_CLASS_LIGHT,
 
     /* The input device is a touchpad, requiring an on-screen cursor. */
-    TOUCHPAD = 0x00010000,
+    TOUCHPAD = android::os::IInputConstants::DEVICE_CLASS_TOUCHPAD,
 
     /* The input device is virtual (not a real device, not part of UI configuration). */
-    VIRTUAL = 0x40000000,
+    VIRTUAL = android::os::IInputConstants::DEVICE_CLASS_VIRTUAL,
 
     /* The input device is external (not built-in). */
-    EXTERNAL = 0x80000000,
+    EXTERNAL = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL,
 };
 
 enum class SysfsClass : uint32_t {
@@ -275,8 +279,8 @@
      */
     virtual std::optional<PropertyMap> getConfiguration(int32_t deviceId) const = 0;
 
-    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                         RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+    virtual std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                                   int axis) const = 0;
 
     virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
 
@@ -336,8 +340,7 @@
     virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
     virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
-    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                          int32_t* outValue) const = 0;
+    virtual std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const = 0;
     /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size
      * (slotCount + 1). The value at the 0 index is set to queried axis. */
     virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -508,8 +511,8 @@
 
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override final;
 
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override final;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override final;
 
     bool hasRelativeAxis(int32_t deviceId, int axis) const override final;
 
@@ -556,8 +559,8 @@
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override final;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId,
                                      int32_t locationKeyCode) const override final;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override final;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId,
+                                                int32_t axis) const override final;
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override final;
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 4c9af2e..086c26f 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -79,6 +79,8 @@
 
     inline bool isIgnored() { return !getMapperCount() && !mController; }
 
+    inline KeyboardType getKeyboardType() const { return mKeyboardType; }
+
     bool isEnabled();
 
     void dump(std::string& dump, const std::string& eventHubDevStr);
@@ -124,6 +126,8 @@
 
     void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode);
 
+    void setKeyboardType(KeyboardType keyboardType);
+
     void bumpGeneration();
 
     [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when);
@@ -196,6 +200,7 @@
     uint32_t mSources;
     bool mIsWaking;
     bool mIsExternal;
+    KeyboardType mKeyboardType = KeyboardType::NONE;
     std::optional<uint8_t> mAssociatedDisplayPort;
     std::optional<std::string> mAssociatedDisplayUniqueIdByPort;
     std::optional<std::string> mAssociatedDisplayUniqueIdByDescriptor;
@@ -301,9 +306,12 @@
         return mEventHub->getDeviceControllerNumber(mId);
     }
     inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const {
-        if (const auto status = mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); status != OK) {
-            return status;
+        std::optional<RawAbsoluteAxisInfo> info = mEventHub->getAbsoluteAxisInfo(mId, code);
+        if (!info.has_value()) {
+            axisInfo->clear();
+            return NAME_NOT_FOUND;
         }
+        *axisInfo = *info;
 
         // Validate axis info for InputDevice.
         if (axisInfo->valid && axisInfo->minValue == axisInfo->maxValue) {
@@ -374,8 +382,8 @@
         return mEventHub->getKeyCodeForKeyLocation(mId, locationKeyCode);
     }
     inline int32_t getSwitchState(int32_t sw) const { return mEventHub->getSwitchState(mId, sw); }
-    inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
-        return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
+    inline std::optional<int32_t> getAbsoluteAxisValue(int32_t code) const {
+        return mEventHub->getAbsoluteAxisValue(mId, code);
     }
     inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis,
                                                               size_t slotCount) const {
@@ -427,9 +435,8 @@
     }
 
     inline bool hasAbsoluteAxis(int32_t code) const {
-        RawAbsoluteAxisInfo info;
-        mEventHub->getAbsoluteAxisInfo(mId, code, &info);
-        return info.valid;
+        std::optional<RawAbsoluteAxisInfo> info = mEventHub->getAbsoluteAxisInfo(mId, code);
+        return info.has_value() && info->valid;
     }
     inline bool isKeyPressed(int32_t scanCode) const {
         return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN;
@@ -437,11 +444,6 @@
     inline bool isKeyCodePressed(int32_t keyCode) const {
         return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN;
     }
-    inline int32_t getAbsoluteAxisValue(int32_t code) const {
-        int32_t value;
-        mEventHub->getAbsoluteAxisValue(mId, code, &value);
-        return value;
-    }
     inline bool isDeviceEnabled() { return mEventHub->isDeviceEnabled(mId); }
     inline status_t enableDevice() { return mEventHub->enableDevice(mId); }
     inline status_t disableDevice() { return mEventHub->disableDevice(mId); }
@@ -470,6 +472,10 @@
     }
     inline void bumpGeneration() { mDevice.bumpGeneration(); }
     inline const PropertyMap& getConfiguration() const { return mDevice.getConfiguration(); }
+    inline KeyboardType getKeyboardType() const { return mDevice.getKeyboardType(); }
+    inline void setKeyboardType(KeyboardType keyboardType) {
+        return mDevice.setKeyboardType(keyboardType);
+    }
 
 private:
     InputDevice& mDevice;
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 7e701c5..6f8c289 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -157,6 +157,7 @@
         void setLastKeyDownTimestamp(nsecs_t when) REQUIRES(mReader->mLock)
                 REQUIRES(mLock) override;
         nsecs_t getLastKeyDownTimestamp() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
+        KeyboardClassifier& getKeyboardClassifier() override;
     } mContext;
 
     friend class ContextImpl;
@@ -176,6 +177,10 @@
 
     // The next stage that should receive the events generated inside InputReader.
     InputListenerInterface& mNextListener;
+
+    // Classifier for keyboard/keyboard-like devices
+    std::unique_ptr<KeyboardClassifier> mKeyboardClassifier;
+
     // As various events are generated inside InputReader, they are stored inside this list. The
     // list can only be accessed with the lock, so the events inside it are well-ordered.
     // Once the reader is done working, these events will be swapped into a temporary storage and
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index 907a49f..e0e0ac2 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <input/InputDevice.h>
+#include <input/KeyboardClassifier.h>
 #include "NotifyArgs.h"
 
 #include <vector>
@@ -64,6 +65,8 @@
 
     virtual void setLastKeyDownTimestamp(nsecs_t when) = 0;
     virtual nsecs_t getLastKeyDownTimestamp() = 0;
+
+    virtual KeyboardClassifier& getKeyboardClassifier() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index 09a5f08..90685de 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -155,8 +155,8 @@
         mMotionAccumulator.finishSync();
     }
 
-    mCursorButtonAccumulator.process(&rawEvent);
-    mMotionAccumulator.process(&rawEvent);
+    mCursorButtonAccumulator.process(rawEvent);
+    mMotionAccumulator.process(rawEvent);
     return out;
 }
 
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index c67314d..20cdb59 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -55,14 +55,14 @@
     mRelY = 0;
 }
 
-void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_REL) {
-        switch (rawEvent->code) {
+void CursorMotionAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_REL) {
+        switch (rawEvent.code) {
             case REL_X:
-                mRelX = rawEvent->value;
+                mRelX = rawEvent.value;
                 break;
             case REL_Y:
-                mRelY = rawEvent->value;
+                mRelY = rawEvent.value;
                 break;
         }
     }
@@ -215,16 +215,16 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> CursorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> CursorInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
     mCursorButtonAccumulator.process(rawEvent);
     mCursorMotionAccumulator.process(rawEvent);
     mCursorScrollAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
         const auto [eventTime, readTime] =
                 applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(),
-                                                   rawEvent->when, rawEvent->readTime,
+                                                   rawEvent.when, rawEvent.readTime,
                                                    mLastEventTime);
         out += sync(eventTime, readTime);
         mLastEventTime = eventTime;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 75ca9c0..2108488 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -34,7 +34,7 @@
     CursorMotionAccumulator();
     void reset(InputDeviceContext& deviceContext);
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void finishSync();
 
     inline int32_t getRelativeX() const { return mRelX; }
@@ -62,7 +62,7 @@
                                                     const InputReaderConfiguration& readerConfig,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
 
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 987d2d0..3af1d04 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -61,13 +61,13 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> ExternalStylusInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> ExternalStylusInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
     mSingleTouchMotionAccumulator.process(rawEvent);
     mTouchButtonAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out += sync(rawEvent.when);
     }
     return out;
 }
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index 97df02b..c040a7b 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -39,7 +39,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 private:
     SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index c7eea0e..2c51448 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -78,7 +78,7 @@
                                                             const InputReaderConfiguration& config,
                                                             ConfigurationChanges changes);
     [[nodiscard]] virtual std::list<NotifyArgs> reset(nsecs_t when);
-    [[nodiscard]] virtual std::list<NotifyArgs> process(const RawEvent* rawEvent) = 0;
+    [[nodiscard]] virtual std::list<NotifyArgs> process(const RawEvent& rawEvent) = 0;
     [[nodiscard]] virtual std::list<NotifyArgs> timeoutExpired(nsecs_t when);
 
     virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 5ce4d30..41e018d 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -259,29 +259,29 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> JoystickInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> JoystickInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    switch (rawEvent->type) {
+    switch (rawEvent.type) {
         case EV_ABS: {
-            auto it = mAxes.find(rawEvent->code);
+            auto it = mAxes.find(rawEvent.code);
             if (it != mAxes.end()) {
                 Axis& axis = it->second;
                 float newValue, highNewValue;
                 switch (axis.axisInfo.mode) {
                     case AxisInfo::MODE_INVERT:
-                        newValue = (axis.rawAxisInfo.maxValue - rawEvent->value) * axis.scale +
+                        newValue = (axis.rawAxisInfo.maxValue - rawEvent.value) * axis.scale +
                                 axis.offset;
                         highNewValue = 0.0f;
                         break;
                     case AxisInfo::MODE_SPLIT:
-                        if (rawEvent->value < axis.axisInfo.splitValue) {
-                            newValue = (axis.axisInfo.splitValue - rawEvent->value) * axis.scale +
+                        if (rawEvent.value < axis.axisInfo.splitValue) {
+                            newValue = (axis.axisInfo.splitValue - rawEvent.value) * axis.scale +
                                     axis.offset;
                             highNewValue = 0.0f;
-                        } else if (rawEvent->value > axis.axisInfo.splitValue) {
+                        } else if (rawEvent.value > axis.axisInfo.splitValue) {
                             newValue = 0.0f;
                             highNewValue =
-                                    (rawEvent->value - axis.axisInfo.splitValue) * axis.highScale +
+                                    (rawEvent.value - axis.axisInfo.splitValue) * axis.highScale +
                                     axis.highOffset;
                         } else {
                             newValue = 0.0f;
@@ -289,7 +289,7 @@
                         }
                         break;
                     default:
-                        newValue = rawEvent->value * axis.scale + axis.offset;
+                        newValue = rawEvent.value * axis.scale + axis.offset;
                         highNewValue = 0.0f;
                         break;
                 }
@@ -300,9 +300,9 @@
         }
 
         case EV_SYN:
-            switch (rawEvent->code) {
+            switch (rawEvent.code) {
                 case SYN_REPORT:
-                    out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false);
+                    out += sync(rawEvent.when, rawEvent.readTime, /*force=*/false);
                     break;
             }
             break;
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index 313f092..621d38b 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -35,7 +35,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 private:
     struct Axis {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 2124555..25f4893 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -21,6 +21,7 @@
 #include "KeyboardInputMapper.h"
 
 #include <ftl/enum.h>
+#include <input/KeyboardClassifier.h>
 #include <ui/Rotation.h>
 
 namespace android {
@@ -96,8 +97,8 @@
 
 KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext,
                                          const InputReaderConfiguration& readerConfig,
-                                         uint32_t source, int32_t keyboardType)
-      : InputMapper(deviceContext, readerConfig), mSource(source), mKeyboardType(keyboardType) {}
+                                         uint32_t source)
+      : InputMapper(deviceContext, readerConfig), mSource(source) {}
 
 uint32_t KeyboardInputMapper::getSources() const {
     return mSource;
@@ -131,7 +132,6 @@
 void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
-    info.setKeyboardType(mKeyboardType);
     info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
 
     std::optional keyboardLayoutInfo = getKeyboardLayoutInfo();
@@ -143,7 +143,6 @@
 void KeyboardInputMapper::dump(std::string& dump) {
     dump += INDENT2 "Keyboard Input Mapper:\n";
     dumpParameters(dump);
-    dump += StringPrintf(INDENT3 "KeyboardType: %d\n", mKeyboardType);
     dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(getOrientation()).c_str());
     dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
     dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
@@ -237,15 +236,15 @@
     return out;
 }
 
-std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    mHidUsageAccumulator.process(*rawEvent);
-    switch (rawEvent->type) {
+    mHidUsageAccumulator.process(rawEvent);
+    switch (rawEvent.type) {
         case EV_KEY: {
-            int32_t scanCode = rawEvent->code;
+            int32_t scanCode = rawEvent.code;
 
             if (isSupportedScanCode(scanCode)) {
-                out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0,
+                out += processKey(rawEvent.when, rawEvent.readTime, rawEvent.value != 0,
                                   scanCode, mHidUsageAccumulator.consumeCurrentHidUsage());
             }
             break;
@@ -327,13 +326,24 @@
         keyMetaState = mMetaState;
     }
 
+    DeviceId deviceId = getDeviceId();
+
+    // On first down: Process key for keyboard classification (will send reconfiguration if the
+    // keyboard type change)
+    if (down && !keyDownIndex) {
+        KeyboardClassifier& classifier = getDeviceContext().getContext()->getKeyboardClassifier();
+        classifier.processKey(deviceId, scanCode, keyMetaState);
+        getDeviceContext().setKeyboardType(classifier.getKeyboardType(deviceId));
+    }
+
+    KeyboardType keyboardType = getDeviceContext().getKeyboardType();
     // Any key down on an external keyboard should wake the device.
     // We don't do this for internal keyboards to prevent them from waking up in your pocket.
     // For internal keyboards and devices for which the default wake behavior is explicitly
     // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
     // wake key individually.
     if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
-        !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) {
+        !(keyboardType != KeyboardType::ALPHABETIC && isMediaKey(keyCode))) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
 
@@ -341,8 +351,8 @@
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
     }
 
-    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                   mSource, getDisplayId(), policyFlags,
+    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId, mSource,
+                                   getDisplayId(), policyFlags,
                                    down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags,
                                    keyCode, scanCode, keyMetaState, downTime));
     return out;
@@ -483,7 +493,6 @@
 void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
     InputReaderContext& context = *getContext();
     context.setLastKeyDownTimestamp(downTime);
-    // TODO(b/338652288): Move cursor fading logic into PointerChoreographer.
     // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
     // shortcuts
     bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode);
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index f2d3f4d..c7df558 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -36,7 +36,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override;
     int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
@@ -61,7 +61,6 @@
     };
 
     uint32_t mSource{};
-    int32_t mKeyboardType{};
     std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
 
     std::vector<KeyDown> mKeyDowns{}; // keys that are down
@@ -85,8 +84,7 @@
     } mParameters{};
 
     KeyboardInputMapper(InputDeviceContext& deviceContext,
-                        const InputReaderConfiguration& readerConfig, uint32_t source,
-                        int32_t keyboardType);
+                        const InputReaderConfiguration& readerConfig, uint32_t source);
     void configureParameters();
     void dumpParameters(std::string& dump) const;
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index bca9d91..1986fe2 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -40,7 +40,7 @@
     return TouchInputMapper::reset(when);
 }
 
-std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
 
     mMultiTouchMotionAccumulator.process(rawEvent);
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 5c173f3..cca2327 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -31,7 +31,7 @@
     ~MultiTouchInputMapper() override;
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 0ddbc06..27ff52f 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -101,12 +101,12 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> RotaryEncoderInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> RotaryEncoderInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
     mRotaryEncoderScrollAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when, rawEvent->readTime);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out += sync(rawEvent.when, rawEvent.readTime);
     }
     return out;
 }
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index fe5d152..14c540b 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -39,7 +39,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 private:
     CursorScrollAccumulator mRotaryEncoderScrollAccumulator;
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index a131e35..d7f2993 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -251,35 +251,35 @@
     mPrevMscTime = static_cast<uint32_t>(mscTime);
 }
 
-std::list<NotifyArgs> SensorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SensorInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    switch (rawEvent->type) {
+    switch (rawEvent.type) {
         case EV_ABS: {
-            auto it = mAxes.find(rawEvent->code);
+            auto it = mAxes.find(rawEvent.code);
             if (it != mAxes.end()) {
                 Axis& axis = it->second;
-                axis.newValue = rawEvent->value * axis.scale + axis.offset;
+                axis.newValue = rawEvent.value * axis.scale + axis.offset;
             }
             break;
         }
 
         case EV_SYN:
-            switch (rawEvent->code) {
+            switch (rawEvent.code) {
                 case SYN_REPORT:
                     for (std::pair<const int32_t, Axis>& pair : mAxes) {
                         Axis& axis = pair.second;
                         axis.currentValue = axis.newValue;
                     }
-                    out += sync(rawEvent->when, /*force=*/false);
+                    out += sync(rawEvent.when, /*force=*/false);
                     break;
             }
             break;
 
         case EV_MSC:
-            switch (rawEvent->code) {
+            switch (rawEvent.code) {
                 case MSC_TIMESTAMP:
                     // hardware timestamp is nano seconds
-                    processHardWareTimestamp(rawEvent->when, rawEvent->value);
+                    processHardWareTimestamp(rawEvent.when, rawEvent.value);
                     break;
             }
     }
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index a55dcd1..63bc151 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -40,7 +40,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
     bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
                       std::chrono::microseconds maxBatchReportLatency) override;
     void disableSensor(InputDeviceSensorType sensorType) override;
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
index ed0e270..140bb0c 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
@@ -30,7 +30,7 @@
     return TouchInputMapper::reset(when);
 }
 
-std::list<NotifyArgs> SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SingleTouchInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
 
     mSingleTouchMotionAccumulator.process(rawEvent);
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
index 7726bfb..bc38711 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -31,7 +31,7 @@
     ~SingleTouchInputMapper() override;
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 protected:
     void syncTouch(nsecs_t when, RawState* outState) override;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index 05338da..f131fb7 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -30,16 +30,16 @@
     return AINPUT_SOURCE_SWITCH;
 }
 
-std::list<NotifyArgs> SwitchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SwitchInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    switch (rawEvent->type) {
+    switch (rawEvent.type) {
         case EV_SW:
-            processSwitch(rawEvent->code, rawEvent->value);
+            processSwitch(rawEvent.code, rawEvent.value);
             break;
 
         case EV_SYN:
-            if (rawEvent->code == SYN_REPORT) {
-                out += sync(rawEvent->when);
+            if (rawEvent.code == SYN_REPORT) {
+                out += sync(rawEvent.when);
             }
     }
     return out;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h
index 2fb48bb..5d8aa2c 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -29,7 +29,7 @@
     virtual ~SwitchInputMapper();
 
     virtual uint32_t getSources() const override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) override;
     virtual void dump(std::string& dump) override;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 8b4b691..81ec24e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -537,18 +537,6 @@
             return getDeviceContext().getAssociatedViewport();
         }
 
-        const std::optional<std::string> associatedDisplayUniqueIdByDescriptor =
-                getDeviceContext().getAssociatedDisplayUniqueIdByDescriptor();
-        if (associatedDisplayUniqueIdByDescriptor) {
-            return getDeviceContext().getAssociatedViewport();
-        }
-
-        const std::optional<std::string> associatedDisplayUniqueIdByPort =
-                getDeviceContext().getAssociatedDisplayUniqueIdByPort();
-        if (associatedDisplayUniqueIdByPort) {
-            return getDeviceContext().getAssociatedViewport();
-        }
-
         if (mDeviceMode == DeviceMode::POINTER) {
             std::optional<DisplayViewport> viewport =
                     mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
@@ -943,7 +931,7 @@
             mSource |= AINPUT_SOURCE_STYLUS;
         }
     } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
-        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD;
         mDeviceMode = DeviceMode::NAVIGATION;
     } else {
         ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
@@ -1415,14 +1403,14 @@
     mExternalStylusFusionTimeout = LLONG_MAX;
 }
 
-std::list<NotifyArgs> TouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> TouchInputMapper::process(const RawEvent& rawEvent) {
     mCursorButtonAccumulator.process(rawEvent);
     mCursorScrollAccumulator.process(rawEvent);
     mTouchButtonAccumulator.process(rawEvent);
 
     std::list<NotifyArgs> out;
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when, rawEvent->readTime);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out += sync(rawEvent.when, rawEvent.readTime);
     }
     return out;
 }
@@ -2355,20 +2343,23 @@
         if (mHaveTilt) {
             float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
             float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
-            orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)));
+            orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)),
+                                         /*isDirectional=*/true);
             tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
         } else {
             tilt = 0;
 
             switch (mCalibration.orientationCalibration) {
                 case Calibration::OrientationCalibration::INTERPOLATED:
-                    orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale);
+                    orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale,
+                                                 /*isDirectional=*/true);
                     break;
                 case Calibration::OrientationCalibration::VECTOR: {
                     int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
                     int32_t c2 = signExtendNybble(in.orientation & 0x0f);
                     if (c1 != 0 || c2 != 0) {
-                        orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f);
+                        orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f,
+                                                     /*isDirectional=*/true);
                         float confidence = hypotf(c1, c2);
                         float scale = 1.0f + confidence / 16.0f;
                         touchMajor *= scale;
@@ -3684,6 +3675,14 @@
     if (mCurrentStreamModifiedByExternalStylus) {
         source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
     }
+    if (mOrientedRanges.orientation.has_value()) {
+        flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION;
+        if (mOrientedRanges.tilt.has_value()) {
+            // In the current implementation, only devices that report a value for tilt supports
+            // directional orientation.
+            flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
+        }
+    }
 
     const ui::LogicalDisplayId displayId =
             getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index b24f2ff..30c58a5 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -174,7 +174,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override;
     int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index b8911db..24efae8 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -24,6 +24,7 @@
 #include <mutex>
 #include <optional>
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 #include <android/input.h>
@@ -241,10 +242,10 @@
         mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
     RawAbsoluteAxisInfo slotAxisInfo;
     deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
-    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
-        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
-              "properly.",
-              deviceContext.getName().c_str());
+    if (!slotAxisInfo.valid || slotAxisInfo.maxValue < 0) {
+        LOG(WARNING) << "Touchpad " << deviceContext.getName()
+                     << " doesn't have a valid ABS_MT_SLOT axis, and probably won't work properly.";
+        slotAxisInfo.maxValue = 0;
     }
     mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
 
@@ -416,17 +417,17 @@
     mResettingInterpreter = false;
 }
 
-std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent& rawEvent) {
     if (mPointerCaptured) {
-        return mCapturedEventConverter.process(*rawEvent);
+        return mCapturedEventConverter.process(rawEvent);
     }
     if (mMotionAccumulator.getActiveSlotsCount() == 0) {
-        mGestureStartTime = rawEvent->when;
+        mGestureStartTime = rawEvent.when;
     }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
         updatePalmDetectionMetrics();
-        return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
+        return sendHardwareState(rawEvent.when, rawEvent.readTime, *state);
     } else {
         return {};
     }
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 546fa5b..8baa63e 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -56,7 +56,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
     [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
 
     void consumeGesture(const Gesture* gesture);
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index 8d78d0f..a3a48ef 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -36,7 +36,7 @@
     info.setVibrator(true);
 }
 
-std::list<NotifyArgs> VibratorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> VibratorInputMapper::process(const RawEvent& rawEvent) {
     // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
     return {};
 }
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index 9079c73..7519682 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -30,7 +30,7 @@
 
     virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     [[nodiscard]] std::list<NotifyArgs> vibrate(const VibrationSequence& sequence, ssize_t repeat,
                                                 int32_t token) override;
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
index 153236c..9e722d4 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
@@ -47,32 +47,32 @@
     mBtnTask = 0;
 }
 
-void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_KEY) {
-        switch (rawEvent->code) {
+void CursorButtonAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_KEY) {
+        switch (rawEvent.code) {
             case BTN_LEFT:
-                mBtnLeft = rawEvent->value;
+                mBtnLeft = rawEvent.value;
                 break;
             case BTN_RIGHT:
-                mBtnRight = rawEvent->value;
+                mBtnRight = rawEvent.value;
                 break;
             case BTN_MIDDLE:
-                mBtnMiddle = rawEvent->value;
+                mBtnMiddle = rawEvent.value;
                 break;
             case BTN_BACK:
-                mBtnBack = rawEvent->value;
+                mBtnBack = rawEvent.value;
                 break;
             case BTN_SIDE:
-                mBtnSide = rawEvent->value;
+                mBtnSide = rawEvent.value;
                 break;
             case BTN_FORWARD:
-                mBtnForward = rawEvent->value;
+                mBtnForward = rawEvent.value;
                 break;
             case BTN_EXTRA:
-                mBtnExtra = rawEvent->value;
+                mBtnExtra = rawEvent.value;
                 break;
             case BTN_TASK:
-                mBtnTask = rawEvent->value;
+                mBtnTask = rawEvent.value;
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index 6960644..256b2bb 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -29,7 +29,7 @@
     CursorButtonAccumulator();
     void reset(const InputDeviceContext& deviceContext);
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
 
     uint32_t getButtonState() const;
     inline bool isLeftPressed() const { return mBtnLeft; }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
index 0714694..f85cab2 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
@@ -39,14 +39,14 @@
     mRelHWheel = 0;
 }
 
-void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_REL) {
-        switch (rawEvent->code) {
+void CursorScrollAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_REL) {
+        switch (rawEvent.code) {
             case REL_WHEEL:
-                mRelWheel = rawEvent->value;
+                mRelWheel = rawEvent.value;
                 break;
             case REL_HWHEEL:
-                mRelHWheel = rawEvent->value;
+                mRelHWheel = rawEvent.value;
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
index ae1b7a3..e563620 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
@@ -31,7 +31,7 @@
     void configure(InputDeviceContext& deviceContext);
     void reset(InputDeviceContext& deviceContext);
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void finishSync();
 
     inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index b3f1700..8dc6e4d 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -45,12 +45,12 @@
     mCurrentSlot = -1;
 }
 
-void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_ABS) {
+void MultiTouchMotionAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_ABS) {
         bool newSlot = false;
         if (mUsingSlotsProtocol) {
-            if (rawEvent->code == ABS_MT_SLOT) {
-                mCurrentSlot = rawEvent->value;
+            if (rawEvent.code == ABS_MT_SLOT) {
+                mCurrentSlot = rawEvent.value;
                 newSlot = true;
             }
         } else if (mCurrentSlot < 0) {
@@ -72,12 +72,12 @@
             if (!mUsingSlotsProtocol) {
                 slot.mInUse = true;
             }
-            if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) {
-                warnIfNotInUse(*rawEvent, slot);
+            if (rawEvent.code == ABS_MT_POSITION_X || rawEvent.code == ABS_MT_POSITION_Y) {
+                warnIfNotInUse(rawEvent, slot);
             }
-            slot.populateAxisValue(rawEvent->code, rawEvent->value);
+            slot.populateAxisValue(rawEvent.code, rawEvent.value);
         }
-    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+    } else if (rawEvent.type == EV_SYN && rawEvent.code == SYN_MT_REPORT) {
         // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
         mCurrentSlot += 1;
     }
@@ -139,13 +139,11 @@
     if (!mUsingSlotsProtocol) {
         return;
     }
-    int32_t initialSlot;
-    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        status == OK) {
-        mCurrentSlot = initialSlot;
+    if (const std::optional<int32_t> initialSlot = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT);
+        initialSlot.has_value()) {
+        mCurrentSlot = initialSlot.value();
     } else {
-        ALOGE("Could not retrieve current multi-touch slot index. status=%s",
-              statusToString(status).c_str());
+        ALOGE("Could not retrieve current multi-touch slot index");
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index a0f2147..388ed82 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -76,7 +76,7 @@
     void configure(const InputDeviceContext& deviceContext, size_t slotCount,
                    bool usingSlotsProtocol);
     void reset(const InputDeviceContext& deviceContext);
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void finishSync();
 
     size_t getActiveSlotsCount() const;
diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
index 27b8e40..4cf9243 100644
--- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
@@ -26,13 +26,13 @@
 }
 
 void SingleTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
-    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X);
-    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y);
-    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE);
-    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH);
-    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE);
-    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X);
-    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y);
+    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X).value_or(0);
+    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y).value_or(0);
+    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE).value_or(0);
+    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH).value_or(0);
+    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE).value_or(0);
+    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X).value_or(0);
+    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y).value_or(0);
 }
 
 void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
@@ -45,29 +45,29 @@
     mAbsTiltY = 0;
 }
 
-void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_ABS) {
-        switch (rawEvent->code) {
+void SingleTouchMotionAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_ABS) {
+        switch (rawEvent.code) {
             case ABS_X:
-                mAbsX = rawEvent->value;
+                mAbsX = rawEvent.value;
                 break;
             case ABS_Y:
-                mAbsY = rawEvent->value;
+                mAbsY = rawEvent.value;
                 break;
             case ABS_PRESSURE:
-                mAbsPressure = rawEvent->value;
+                mAbsPressure = rawEvent.value;
                 break;
             case ABS_TOOL_WIDTH:
-                mAbsToolWidth = rawEvent->value;
+                mAbsToolWidth = rawEvent.value;
                 break;
             case ABS_DISTANCE:
-                mAbsDistance = rawEvent->value;
+                mAbsDistance = rawEvent.value;
                 break;
             case ABS_TILT_X:
-                mAbsTiltX = rawEvent->value;
+                mAbsTiltX = rawEvent.value;
                 break;
             case ABS_TILT_Y:
-                mAbsTiltY = rawEvent->value;
+                mAbsTiltY = rawEvent.value;
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h
index 93056f0..fb74bca 100644
--- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h
@@ -28,7 +28,7 @@
 public:
     SingleTouchMotionAccumulator();
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void reset(InputDeviceContext& deviceContext);
 
     inline int32_t getAbsoluteX() const { return mAbsX; }
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 8c4bed3..ba8577e 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -52,60 +52,60 @@
     mHidUsageAccumulator.reset();
 }
 
-void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
-    mHidUsageAccumulator.process(*rawEvent);
+void TouchButtonAccumulator::process(const RawEvent& rawEvent) {
+    mHidUsageAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_KEY) {
-        switch (rawEvent->code) {
+    if (rawEvent.type == EV_KEY) {
+        switch (rawEvent.code) {
             case BTN_TOUCH:
-                mBtnTouch = rawEvent->value;
+                mBtnTouch = rawEvent.value;
                 break;
             case BTN_STYLUS:
-                mBtnStylus = rawEvent->value;
+                mBtnStylus = rawEvent.value;
                 break;
             case BTN_STYLUS2:
             case BTN_0: // BTN_0 is what gets mapped for the HID usage
                         // Digitizers.SecondaryBarrelSwitch
-                mBtnStylus2 = rawEvent->value;
+                mBtnStylus2 = rawEvent.value;
                 break;
             case BTN_TOOL_FINGER:
-                mBtnToolFinger = rawEvent->value;
+                mBtnToolFinger = rawEvent.value;
                 break;
             case BTN_TOOL_PEN:
-                mBtnToolPen = rawEvent->value;
+                mBtnToolPen = rawEvent.value;
                 break;
             case BTN_TOOL_RUBBER:
-                mBtnToolRubber = rawEvent->value;
+                mBtnToolRubber = rawEvent.value;
                 break;
             case BTN_TOOL_BRUSH:
-                mBtnToolBrush = rawEvent->value;
+                mBtnToolBrush = rawEvent.value;
                 break;
             case BTN_TOOL_PENCIL:
-                mBtnToolPencil = rawEvent->value;
+                mBtnToolPencil = rawEvent.value;
                 break;
             case BTN_TOOL_AIRBRUSH:
-                mBtnToolAirbrush = rawEvent->value;
+                mBtnToolAirbrush = rawEvent.value;
                 break;
             case BTN_TOOL_MOUSE:
-                mBtnToolMouse = rawEvent->value;
+                mBtnToolMouse = rawEvent.value;
                 break;
             case BTN_TOOL_LENS:
-                mBtnToolLens = rawEvent->value;
+                mBtnToolLens = rawEvent.value;
                 break;
             case BTN_TOOL_DOUBLETAP:
-                mBtnToolDoubleTap = rawEvent->value;
+                mBtnToolDoubleTap = rawEvent.value;
                 break;
             case BTN_TOOL_TRIPLETAP:
-                mBtnToolTripleTap = rawEvent->value;
+                mBtnToolTripleTap = rawEvent.value;
                 break;
             case BTN_TOOL_QUADTAP:
-                mBtnToolQuadTap = rawEvent->value;
+                mBtnToolQuadTap = rawEvent.value;
                 break;
             case BTN_TOOL_QUINTTAP:
-                mBtnToolQuintTap = rawEvent->value;
+                mBtnToolQuintTap = rawEvent.value;
                 break;
             default:
-                processMappedKey(rawEvent->code, rawEvent->value);
+                processMappedKey(rawEvent.code, rawEvent.value);
         }
         return;
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index e829692..c7adf84 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -33,7 +33,7 @@
     void configure();
     void reset();
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
 
     uint32_t getButtonState() const;
     ToolType getToolType() const;
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
index b89b7f3..6885adb 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -40,15 +40,15 @@
 }
 
 std::optional<SelfContainedHardwareState> HardwareStateConverter::processRawEvent(
-        const RawEvent* rawEvent) {
+        const RawEvent& rawEvent) {
     std::optional<SelfContainedHardwareState> out;
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out = produceHardwareState(rawEvent->when);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out = produceHardwareState(rawEvent.when);
         mMotionAccumulator.finishSync();
         mMscTimestamp = 0;
     }
-    if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
-        mMscTimestamp = rawEvent->value;
+    if (rawEvent.type == EV_MSC && rawEvent.code == MSC_TIMESTAMP) {
+        mMscTimestamp = rawEvent.value;
     }
     mCursorButtonAccumulator.process(rawEvent);
     mMotionAccumulator.process(rawEvent);
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
index 633448e..07e62c6 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -44,7 +44,7 @@
     HardwareStateConverter(const InputDeviceContext& deviceContext,
                            MultiTouchMotionAccumulator& motionAccumulator);
 
-    std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent* event);
+    std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent& event);
     void reset();
 
 private:
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
index 69264f8..f4a5e0d 100644
--- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
@@ -90,7 +90,7 @@
     // prefixed with "gestureProp." and have spaces replaced by underscores. So, for example, the
     // configuration key "gestureProp.Palm_Width" refers to the "Palm Width" property.
     const std::string gesturePropPrefix = "gestureProp.";
-    for (const std::string key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) {
+    for (const std::string& key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) {
         std::string propertyName = key.substr(gesturePropPrefix.length());
         for (size_t i = 0; i < propertyName.length(); i++) {
             if (propertyName[i] == '_') {
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 255c7eb..5b7cc2d 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -47,6 +47,7 @@
         "liblog_rust",
         "liblogger",
         "libnix",
+        "libinput_rust",
     ],
     host_supported: true,
 }
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index 6df339e..8b44af3 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -31,6 +31,7 @@
 use crate::input_filter_thread::InputFilterThread;
 use crate::slow_keys_filter::SlowKeysFilter;
 use crate::sticky_keys_filter::StickyKeysFilter;
+use input::ModifierState;
 use log::{error, info};
 use std::sync::{Arc, Mutex, RwLock};
 
@@ -169,12 +170,15 @@
         Self(callbacks)
     }
 
-    pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
-        let _ = self
-            .0
-            .read()
-            .unwrap()
-            .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
+    pub fn modifier_state_changed(
+        &self,
+        modifier_state: ModifierState,
+        locked_modifier_state: ModifierState,
+    ) {
+        let _ = self.0.read().unwrap().onModifierStateChanged(
+            modifier_state.bits() as i32,
+            locked_modifier_state.bits() as i32,
+        );
     }
 }
 
@@ -396,14 +400,15 @@
         IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
         KeyEvent::KeyEvent,
     };
+    use input::ModifierState;
     use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
     use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard};
     use std::time::Duration;
 
     #[derive(Default)]
     struct TestCallbacksInner {
-        last_modifier_state: u32,
-        last_locked_modifier_state: u32,
+        last_modifier_state: ModifierState,
+        last_locked_modifier_state: ModifierState,
         last_event: Option<KeyEvent>,
         test_thread: Option<FakeCppThread>,
     }
@@ -428,15 +433,15 @@
 
         pub fn clear(&mut self) {
             self.inner().last_event = None;
-            self.inner().last_modifier_state = 0;
-            self.inner().last_locked_modifier_state = 0;
+            self.inner().last_modifier_state = ModifierState::None;
+            self.inner().last_locked_modifier_state = ModifierState::None;
         }
 
-        pub fn get_last_modifier_state(&self) -> u32 {
+        pub fn get_last_modifier_state(&self) -> ModifierState {
             self.0.read().unwrap().last_modifier_state
         }
 
-        pub fn get_last_locked_modifier_state(&self) -> u32 {
+        pub fn get_last_locked_modifier_state(&self) -> ModifierState {
             self.0.read().unwrap().last_locked_modifier_state
         }
 
@@ -459,8 +464,10 @@
             modifier_state: i32,
             locked_modifier_state: i32,
         ) -> std::result::Result<(), binder::Status> {
-            self.inner().last_modifier_state = modifier_state as u32;
-            self.inner().last_locked_modifier_state = locked_modifier_state as u32;
+            self.inner().last_modifier_state =
+                ModifierState::from_bits(modifier_state as u32).unwrap();
+            self.inner().last_locked_modifier_state =
+                ModifierState::from_bits(locked_modifier_state as u32).unwrap();
             Result::Ok(())
         }
 
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index 6c2277c..6c7c7fb 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -23,6 +23,7 @@
 use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
     DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
 };
+use input::ModifierState;
 use std::collections::HashSet;
 
 // Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
@@ -40,20 +41,6 @@
 const KEYCODE_FUNCTION: i32 = 119;
 const KEYCODE_NUM_LOCK: i32 = 143;
 
-// Modifier states: values are from /frameworks/native/include/android/input.h
-const META_ALT_ON: u32 = 0x02;
-const META_ALT_LEFT_ON: u32 = 0x10;
-const META_ALT_RIGHT_ON: u32 = 0x20;
-const META_SHIFT_ON: u32 = 0x01;
-const META_SHIFT_LEFT_ON: u32 = 0x40;
-const META_SHIFT_RIGHT_ON: u32 = 0x80;
-const META_CTRL_ON: u32 = 0x1000;
-const META_CTRL_LEFT_ON: u32 = 0x2000;
-const META_CTRL_RIGHT_ON: u32 = 0x4000;
-const META_META_ON: u32 = 0x10000;
-const META_META_LEFT_ON: u32 = 0x20000;
-const META_META_RIGHT_ON: u32 = 0x40000;
-
 pub struct StickyKeysFilter {
     next: Box<dyn Filter + Send + Sync>,
     listener: ModifierStateListener,
@@ -61,11 +48,11 @@
     contributing_devices: HashSet<i32>,
     /// State describing the current enabled modifiers. This contain both locked and non-locked
     /// modifier state bits.
-    modifier_state: u32,
+    modifier_state: ModifierState,
     /// State describing the current locked modifiers. These modifiers will not be cleared on a
     /// non-modifier key press. They will be cleared only if the locked modifier key is pressed
     /// again.
-    locked_modifier_state: u32,
+    locked_modifier_state: ModifierState,
 }
 
 impl StickyKeysFilter {
@@ -78,8 +65,8 @@
             next,
             listener,
             contributing_devices: HashSet::new(),
-            modifier_state: 0,
-            locked_modifier_state: 0,
+            modifier_state: ModifierState::None,
+            locked_modifier_state: ModifierState::None,
         }
     }
 }
@@ -93,12 +80,12 @@
             // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
             // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
             // the KeyEvent.
-            let old_modifier_state = event.metaState as u32;
+            let old_modifier_state = ModifierState::from_bits(event.metaState as u32).unwrap();
             let mut new_event = *event;
             // Send the current modifier state with the key event before clearing non-locked
             // modifier state
             new_event.metaState =
-                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32;
+                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state).bits() as i32;
             self.next.notify_key(&new_event);
             if up && !is_modifier_key(event.keyCode) {
                 modifier_state =
@@ -110,10 +97,10 @@
             // If ephemeral modifier key, capture the key and update the sticky modifier states
             let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
             let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
-            if locked_modifier_state & modifier_key_mask != 0 {
+            if locked_modifier_state & modifier_key_mask != ModifierState::None {
                 locked_modifier_state &= !symmetrical_modifier_key_mask;
                 modifier_state &= !symmetrical_modifier_key_mask;
-            } else if modifier_key_mask & modifier_state != 0 {
+            } else if modifier_key_mask & modifier_state != ModifierState::None {
                 locked_modifier_state |= modifier_key_mask;
                 modifier_state =
                     (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask;
@@ -134,11 +121,12 @@
         // Clear state if all contributing devices removed
         self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
         if self.contributing_devices.is_empty()
-            && (self.modifier_state != 0 || self.locked_modifier_state != 0)
+            && (self.modifier_state != ModifierState::None
+                || self.locked_modifier_state != ModifierState::None)
         {
-            self.modifier_state = 0;
-            self.locked_modifier_state = 0;
-            self.listener.modifier_state_changed(0, 0);
+            self.modifier_state = ModifierState::None;
+            self.locked_modifier_state = ModifierState::None;
+            self.listener.modifier_state_changed(ModifierState::None, ModifierState::None);
         }
         self.next.notify_devices_changed(device_infos);
     }
@@ -181,51 +169,53 @@
     )
 }
 
-fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 {
+fn get_ephemeral_modifier_key_mask(keycode: i32) -> ModifierState {
     match keycode {
-        KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON,
-        KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON,
-        KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON,
-        KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON,
-        KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON,
-        KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON,
-        KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON,
-        KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON,
-        _ => 0,
+        KEYCODE_ALT_LEFT => ModifierState::AltLeftOn | ModifierState::AltOn,
+        KEYCODE_ALT_RIGHT => ModifierState::AltRightOn | ModifierState::AltOn,
+        KEYCODE_SHIFT_LEFT => ModifierState::ShiftLeftOn | ModifierState::ShiftOn,
+        KEYCODE_SHIFT_RIGHT => ModifierState::ShiftRightOn | ModifierState::ShiftOn,
+        KEYCODE_CTRL_LEFT => ModifierState::CtrlLeftOn | ModifierState::CtrlOn,
+        KEYCODE_CTRL_RIGHT => ModifierState::CtrlRightOn | ModifierState::CtrlOn,
+        KEYCODE_META_LEFT => ModifierState::MetaLeftOn | ModifierState::MetaOn,
+        KEYCODE_META_RIGHT => ModifierState::MetaRightOn | ModifierState::MetaOn,
+        _ => ModifierState::None,
     }
 }
 
 /// Modifier mask including both left and right versions of a modifier key.
-fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 {
+fn get_symmetrical_modifier_key_mask(keycode: i32) -> ModifierState {
     match keycode {
-        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => {
+            ModifierState::AltLeftOn | ModifierState::AltRightOn | ModifierState::AltOn
+        }
         KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => {
-            META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON
+            ModifierState::ShiftLeftOn | ModifierState::ShiftRightOn | ModifierState::ShiftOn
         }
         KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => {
-            META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlRightOn | ModifierState::CtrlOn
         }
         KEYCODE_META_LEFT | KEYCODE_META_RIGHT => {
-            META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON
+            ModifierState::MetaLeftOn | ModifierState::MetaRightOn | ModifierState::MetaOn
         }
-        _ => 0,
+        _ => ModifierState::None,
     }
 }
 
-fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 {
+fn clear_ephemeral_modifier_state(modifier_state: ModifierState) -> ModifierState {
     modifier_state
-        & !(META_ALT_LEFT_ON
-            | META_ALT_RIGHT_ON
-            | META_ALT_ON
-            | META_SHIFT_LEFT_ON
-            | META_SHIFT_RIGHT_ON
-            | META_SHIFT_ON
-            | META_CTRL_LEFT_ON
-            | META_CTRL_RIGHT_ON
-            | META_CTRL_ON
-            | META_META_LEFT_ON
-            | META_META_RIGHT_ON
-            | META_META_ON)
+        & !(ModifierState::AltLeftOn
+            | ModifierState::AltRightOn
+            | ModifierState::AltOn
+            | ModifierState::ShiftLeftOn
+            | ModifierState::ShiftRightOn
+            | ModifierState::ShiftOn
+            | ModifierState::CtrlLeftOn
+            | ModifierState::CtrlRightOn
+            | ModifierState::CtrlOn
+            | ModifierState::MetaLeftOn
+            | ModifierState::MetaRightOn
+            | ModifierState::MetaOn)
 }
 
 #[cfg(test)]
@@ -237,9 +227,7 @@
         StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK,
         KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT,
         KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT,
-        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON,
-        META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON,
-        META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON,
+        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM,
     };
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
     use binder::Strong;
@@ -247,6 +235,7 @@
         DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
         KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
     };
+    use input::ModifierState;
     use std::sync::{Arc, RwLock};
 
     static DEVICE_ID: i32 = 1;
@@ -347,30 +336,30 @@
             Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
         );
         let test_states = &[
-            (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON),
-            (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON),
-            (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON),
-            (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON),
-            (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON),
-            (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON),
-            (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON),
-            (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON),
+            (KEYCODE_ALT_LEFT, ModifierState::AltOn | ModifierState::AltLeftOn),
+            (KEYCODE_ALT_RIGHT, ModifierState::AltOn | ModifierState::AltRightOn),
+            (KEYCODE_CTRL_LEFT, ModifierState::CtrlOn | ModifierState::CtrlLeftOn),
+            (KEYCODE_CTRL_RIGHT, ModifierState::CtrlOn | ModifierState::CtrlRightOn),
+            (KEYCODE_SHIFT_LEFT, ModifierState::ShiftOn | ModifierState::ShiftLeftOn),
+            (KEYCODE_SHIFT_RIGHT, ModifierState::ShiftOn | ModifierState::ShiftRightOn),
+            (KEYCODE_META_LEFT, ModifierState::MetaOn | ModifierState::MetaLeftOn),
+            (KEYCODE_META_RIGHT, ModifierState::MetaOn | ModifierState::MetaRightOn),
         ];
         for test_state in test_states.iter() {
             test_filter.clear();
             test_callbacks.clear();
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
-            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
             assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
             // Re-send keys to lock it
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
             assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
             assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
@@ -382,8 +371,8 @@
             assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
 
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
-            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
         }
     }
 
@@ -398,14 +387,17 @@
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
 
-        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
-        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
 
-        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
     }
 
     #[test]
@@ -427,20 +419,26 @@
 
         assert_eq!(
             test_callbacks.get_last_modifier_state(),
-            META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::ShiftLeftOn
+                | ModifierState::ShiftOn
+                | ModifierState::CtrlLeftOn
+                | ModifierState::CtrlOn
         );
         assert_eq!(
             test_callbacks.get_last_locked_modifier_state(),
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
         );
 
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
 
-        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
         assert_eq!(
             test_callbacks.get_last_locked_modifier_state(),
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
         );
     }
 
@@ -458,13 +456,13 @@
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
         assert_eq!(
             test_filter.last_event().unwrap().metaState as u32,
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            (ModifierState::CtrlLeftOn | ModifierState::CtrlOn).bits()
         );
 
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
         assert_eq!(
             test_filter.last_event().unwrap().metaState as u32,
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            (ModifierState::CtrlLeftOn | ModifierState::CtrlOn).bits()
         );
     }
 
@@ -499,15 +497,18 @@
         });
 
         sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
-        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
         assert_eq!(
             test_callbacks.get_last_locked_modifier_state(),
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
         );
 
         sticky_keys_filter.notify_devices_changed(&[]);
-        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
     }
 
     fn setup_filter(
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 9b5db23..cf0d46a 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -109,7 +109,6 @@
     },
     static_libs: [
         "libflagtest",
-        "libc++fs",
         "libgmock",
     ],
     require_root: true,
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index daa000f..12736c8 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -16,6 +16,8 @@
 
 #include "FakeEventHub.h"
 
+#include <optional>
+
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
@@ -263,18 +265,16 @@
     return device->configuration;
 }
 
-status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                           RawAbsoluteAxisInfo* outAxisInfo) const {
+std::optional<RawAbsoluteAxisInfo> FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId,
+                                                                     int axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxes.indexOfKey(axis);
         if (index >= 0) {
-            *outAxisInfo = device->absoluteAxes.valueAt(index);
-            return OK;
+            return device->absoluteAxes.valueAt(index);
         }
     }
-    outAxisInfo->clear();
-    return -1;
+    return std::nullopt;
 }
 
 bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -417,18 +417,15 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                            int32_t* outValue) const {
+std::optional<int32_t> FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
         if (index >= 0) {
-            *outValue = device->absoluteAxisValue.valueAt(index);
-            return OK;
+            return device->absoluteAxisValue.valueAt(index);
         }
     }
-    *outValue = 0;
-    return -1;
+    return std::nullopt;
 }
 
 void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis,
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index f07b344..c2c875f 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -168,8 +168,8 @@
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
     int32_t getDeviceControllerNumber(int32_t) const override;
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override;
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override;
     bool hasRelativeAxis(int32_t deviceId, int axis) const override;
     bool hasInputProperty(int32_t, int) const override;
     bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
@@ -187,7 +187,7 @@
     std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
 
     // Return true if the device has non-empty key layout.
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index 530416c..3df05f4 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -215,6 +215,28 @@
     mStaleEventTimeout = timeout;
 }
 
+void FakeInputDispatcherPolicy::setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching) {
+    mConsumeKeyBeforeDispatching = consumeKeyBeforeDispatching;
+}
+
+void FakeInputDispatcherPolicy::assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    if (!mFocusedDisplayNotifiedCondition.wait_for(lock, 100ms,
+                                                   [this, expectedDisplay]() REQUIRES(mLock) {
+                                                       if (!mNotifiedFocusedDisplay.has_value() ||
+                                                           mNotifiedFocusedDisplay.value() !=
+                                                                   expectedDisplay) {
+                                                           return false;
+                                                       }
+                                                       return true;
+                                                   })) {
+        ADD_FAILURE() << "Timed out waiting for notifyFocusedDisplayChanged(" << expectedDisplay
+                      << ") to be called.";
+    }
+}
+
 void FakeInputDispatcherPolicy::assertUserActivityNotPoked() {
     std::unique_lock lock(mLock);
     base::ScopedLockAssertion assumeLocked(mLock);
@@ -401,6 +423,9 @@
 
 nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp<IBinder>&,
                                                                  const KeyEvent&, uint32_t) {
+    if (mConsumeKeyBeforeDispatching) {
+        return -1;
+    }
     nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
     // Clear intercept state so we could dispatch the event in next wake.
     mInterceptKeyTimeout = 0ms;
@@ -466,4 +491,10 @@
     mFilteredEvent = nullptr;
 }
 
+void FakeInputDispatcherPolicy::notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) {
+    std::scoped_lock lock(mLock);
+    mNotifiedFocusedDisplay = displayId;
+    mFocusedDisplayNotifiedCondition.notify_all();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index 2c86146..a0f3ea9 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -115,6 +115,8 @@
     void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler);
     void assertUnhandledKeyReported(int32_t keycode);
     void assertUnhandledKeyNotReported();
+    void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching);
+    void assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay);
 
 private:
     std::mutex mLock;
@@ -125,6 +127,9 @@
 
     std::condition_variable mPointerCaptureChangedCondition;
 
+    std::optional<ui::LogicalDisplayId> mNotifiedFocusedDisplay GUARDED_BY(mLock);
+    std::condition_variable mFocusedDisplayNotifiedCondition;
+
     std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
     // ANR handling
     std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
@@ -144,6 +149,8 @@
 
     std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
 
+    bool mConsumeKeyBeforeDispatching = false;
+
     BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
 
     std::condition_variable mNotifyUnhandledKey;
@@ -198,6 +205,7 @@
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
     void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
                                  const std::set<gui::Uid>& uids) override;
+    void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) override;
 
     void assertFilterInputEventWasCalledInternal(
             const std::function<void(const InputEvent&)>& verify);
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index d2cb0ac..6099c91 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -16,6 +16,7 @@
 
 #include "FakeInputReaderPolicy.h"
 
+#include <android-base/properties.h>
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 
@@ -24,6 +25,12 @@
 
 namespace android {
 
+namespace {
+
+static const int HW_TIMEOUT_MULTIPLIER = base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+
+} // namespace
+
 void FakeInputReaderPolicy::assertInputDevicesChanged() {
     waitForInputDevices([](bool devicesChanged) {
         if (!devicesChanged) {
@@ -241,9 +248,11 @@
     base::ScopedLockAssertion assumeLocked(mLock);
 
     const bool devicesChanged =
-            mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
-                return mInputDevicesChanged;
-            });
+            mDevicesChangedCondition.wait_for(lock,
+                                              ADD_INPUT_DEVICE_TIMEOUT * HW_TIMEOUT_MULTIPLIER,
+                                              [this]() REQUIRES(mLock) {
+                                                  return mInputDevicesChanged;
+                                              });
     ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
     mInputDevicesChanged = false;
 }
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 456013e..d0998ba 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -77,10 +77,12 @@
 }
 
 void FakePointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) {
+    mDisplaysToSkipScreenshotFlagChanged = true;
     mDisplaysToSkipScreenshot.insert(displayId);
 }
 
 void FakePointerController::clearSkipScreenshotFlags() {
+    mDisplaysToSkipScreenshotFlagChanged = true;
     mDisplaysToSkipScreenshot.clear();
 }
 
@@ -125,13 +127,21 @@
     ASSERT_EQ(std::nullopt, mCustomIconStyle);
 }
 
-void FakePointerController::assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId,
-                                                             bool isHidden) {
-    if (isHidden) {
-        ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end());
-    } else {
-        ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end());
-    }
+void FakePointerController::assertIsSkipScreenshotFlagSet(ui::LogicalDisplayId displayId) {
+    ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end());
+}
+
+void FakePointerController::assertIsSkipScreenshotFlagNotSet(ui::LogicalDisplayId displayId) {
+    ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end());
+}
+
+void FakePointerController::assertSkipScreenshotFlagChanged() {
+    ASSERT_TRUE(mDisplaysToSkipScreenshotFlagChanged);
+    mDisplaysToSkipScreenshotFlagChanged = false;
+}
+
+void FakePointerController::assertSkipScreenshotFlagNotChanged() {
+    ASSERT_FALSE(mDisplaysToSkipScreenshotFlagChanged);
 }
 
 bool FakePointerController::isPointerShown() {
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 8d95f65..2c76c62 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -57,7 +57,10 @@
     void assertPointerIconNotSet();
     void assertCustomPointerIconSet(PointerIconStyle iconId);
     void assertCustomPointerIconNotSet();
-    void assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId, bool isHidden);
+    void assertIsSkipScreenshotFlagSet(ui::LogicalDisplayId displayId);
+    void assertIsSkipScreenshotFlagNotSet(ui::LogicalDisplayId displayId);
+    void assertSkipScreenshotFlagChanged();
+    void assertSkipScreenshotFlagNotChanged();
     bool isPointerShown();
 
 private:
@@ -81,6 +84,7 @@
 
     std::map<ui::LogicalDisplayId, std::vector<int32_t>> mSpotsByDisplay;
     std::unordered_set<ui::LogicalDisplayId> mDisplaysToSkipScreenshot;
+    bool mDisplaysToSkipScreenshotFlagChanged{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index 36a8f00..ee65d3a 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -304,15 +304,11 @@
                                           WithFlags(expectedFlags)));
     }
 
-    inline void consumeMotionPointerUp(
-            int32_t pointerIdx,
-            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
-            int32_t expectedFlags = 0) {
+    inline void consumeMotionPointerUp(int32_t pointerIdx,
+                                       const ::testing::Matcher<MotionEvent>& matcher) {
         const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeMotionEvent(testing::AllOf(WithMotionAction(action),
-                                          WithDisplayId(expectedDisplayId),
-                                          WithFlags(expectedFlags)));
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher));
     }
 
     inline void consumeMotionUp(
diff --git a/services/inputflinger/tests/HardwareProperties_test.cpp b/services/inputflinger/tests/HardwareProperties_test.cpp
index 8dfa8c8..643fab6 100644
--- a/services/inputflinger/tests/HardwareProperties_test.cpp
+++ b/services/inputflinger/tests/HardwareProperties_test.cpp
@@ -48,24 +48,20 @@
     static constexpr int32_t EVENTHUB_ID = 1;
 
     void setupValidAxis(int axis, int32_t min, int32_t max, int32_t resolution) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = true;
-                    outAxisInfo->minValue = min;
-                    outAxisInfo->maxValue = max;
-                    outAxisInfo->flat = 0;
-                    outAxisInfo->fuzz = 0;
-                    outAxisInfo->resolution = resolution;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::optional<RawAbsoluteAxisInfo>{{
+                        .valid = true,
+                        .minValue = min,
+                        .maxValue = max,
+                        .flat = 0,
+                        .fuzz = 0,
+                        .resolution = resolution,
+                }}));
     }
 
     void setupInvalidAxis(int axis) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = false;
-                    return -1;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::nullopt));
     }
 
     void setProperty(int property, bool value) {
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index ff9bd9e..34c81fc 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -81,7 +81,7 @@
         event.type = type;
         event.code = code;
         event.value = value;
-        std::optional<SelfContainedHardwareState> schs = mConverter->processRawEvent(&event);
+        std::optional<SelfContainedHardwareState> schs = mConverter->processRawEvent(event);
         EXPECT_FALSE(schs.has_value());
     }
 
@@ -93,7 +93,7 @@
         event.type = EV_SYN;
         event.code = SYN_REPORT;
         event.value = 0;
-        return mConverter->processRawEvent(&event);
+        return mConverter->processRawEvent(event);
     }
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 8de28c6..5eab6be 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -610,28 +610,6 @@
     return args;
 }
 
-static NotifyKeyArgs generateSystemShortcutArgs(
-        int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
-    nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    // Define a valid key event.
-    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
-                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C,
-                       AMETA_META_ON, currentTime);
-
-    return args;
-}
-
-static NotifyKeyArgs generateAssistantKeyArgs(
-        int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
-    nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    // Define a valid key event.
-    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
-                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST,
-                       KEY_ASSISTANT, AMETA_NONE, currentTime);
-
-    return args;
-}
-
 [[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
                                                          ui::LogicalDisplayId displayId,
                                                          const std::vector<PointF>& points) {
@@ -1304,9 +1282,11 @@
               injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    foregroundWindow->consumeMotionPointerUp(0);
-    wallpaperWindow->consumeMotionPointerUp(0, ui::LogicalDisplayId::DEFAULT,
-                                            EXPECTED_WALLPAPER_FLAGS);
+    foregroundWindow->consumeMotionPointerUp(/*pointerIdx=*/0,
+                                             WithDisplayId(ui::LogicalDisplayId::DEFAULT));
+    wallpaperWindow->consumeMotionPointerUp(/*pointerIdx=*/0,
+                                            AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -6242,8 +6222,10 @@
                                                  {touchPoint, touchPoint}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT,
-                                         AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionPointerUp(/*pointerIdx=*/1,
+                                         AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                               WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
+                                               WithPointerCount(2)));
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
@@ -6385,8 +6367,10 @@
                                                  {pointInFirst, pointInSecond}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT,
-                                         AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionPointerUp(/*pointerIdx=*/1,
+                                         AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                               WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
+                                               WithPointerCount(2)));
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
@@ -6628,17 +6612,18 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
 
     // Window should receive key down event.
     window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
-    // Should have poked user activity
+    // Should have not poked user activity
     mDispatcher->waitForIdle();
     mFakePolicy->assertUserActivityNotPoked();
 }
 
-TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) {
+TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceivePolicyConsumedKey) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
@@ -6650,41 +6635,20 @@
 
     window->consumeFocusEvent(true);
 
+    mFakePolicy->setConsumeKeyBeforeDispatching(true);
+
     mDispatcher->notifyKey(
-            generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
     mDispatcher->waitForIdle();
 
-    // System key is not passed down
+    // Key is not passed down
     window->assertNoEvents();
 
     // Should have poked user activity
     mFakePolicy->assertUserActivityPoked();
 }
 
-TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) {
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
-                                       ui::LogicalDisplayId::DEFAULT);
-
-    window->setFocusable(true);
-    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
-    setFocusedWindow(window);
-
-    window->consumeFocusEvent(true);
-
-    mDispatcher->notifyKey(
-            generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
-    mDispatcher->waitForIdle();
-
-    // System key is not passed down
-    window->assertNoEvents();
-
-    // Should have poked user activity
-    mFakePolicy->assertUserActivityPoked();
-}
-
-TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) {
+TEST_F(InputDispatcherTest, FocusedWindow_PolicyConsumedKeyIgnoresDisableUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
@@ -6697,8 +6661,10 @@
 
     window->consumeFocusEvent(true);
 
+    mFakePolicy->setConsumeKeyBeforeDispatching(true);
+
     mDispatcher->notifyKey(
-            generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
     mDispatcher->waitForIdle();
 
     // System key is not passed down
@@ -6708,6 +6674,39 @@
     mFakePolicy->assertUserActivityPoked();
 }
 
+class DisableUserActivityInputDispatcherTest : public InputDispatcherTest,
+                                               public ::testing::WithParamInterface<bool> {};
+
+TEST_P(DisableUserActivityInputDispatcherTest, NotPassedToUserUserActivity) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+
+    window->setDisableUserActivity(GetParam());
+
+    window->setFocusable(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                   .keyCode(AKEYCODE_A)
+                                   .policyFlags(0)
+                                   .build());
+    mDispatcher->waitForIdle();
+
+    // Key is not passed down
+    window->assertNoEvents();
+
+    // Should not have poked user activity
+    mFakePolicy->assertUserActivityNotPoked();
+}
+
+INSTANTIATE_TEST_CASE_P(DisableUserActivity, DisableUserActivityInputDispatcherTest,
+                        ::testing::Bool());
+
 TEST_F(InputDispatcherTest, InjectedTouchesPokeUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
@@ -8584,6 +8583,8 @@
         // Set focus to second display window.
         // Set focus display to second one.
         mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+        mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+
         // Set focus window for second display.
         mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2);
         windowInSecondary->setFocusable(true);
@@ -11054,6 +11055,38 @@
     mWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherPointerCaptureTests, MultiDisplayPointerCapture) {
+    // The default display is the focused display to begin with.
+    requestAndVerifyPointerCapture(mWindow, true);
+
+    // Move the second window to a second display, make it the focused window on that display.
+    mSecondWindow->editInfo()->displayId = SECOND_DISPLAY_ID;
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
+    setFocusedWindow(mSecondWindow);
+    mSecondWindow->consumeFocusEvent(true);
+
+    mWindow->assertNoEvents();
+
+    // The second window cannot gain capture because it is not on the focused display.
+    mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
+    mFakePolicy->assertSetPointerCaptureNotCalled();
+    mSecondWindow->assertNoEvents();
+
+    // Make the second display the focused display.
+    mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+    mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+
+    // This causes the first window to lose pointer capture, and it's unable to request capture.
+    mWindow->consumeCaptureEvent(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
+
+    mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+    mFakePolicy->assertSetPointerCaptureNotCalled();
+
+    // The second window is now able to gain pointer capture successfully.
+    requestAndVerifyPointerCapture(mSecondWindow, true);
+}
+
 using InputDispatcherPointerCaptureDeathTest = InputDispatcherPointerCaptureTests;
 
 TEST_F(InputDispatcherPointerCaptureDeathTest,
@@ -12854,10 +12887,14 @@
 
     // Spy window pilfers the pointers.
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
-    window->consumeMotionPointerUp(/*idx=*/2, ui::LogicalDisplayId::DEFAULT,
-                                   AMOTION_EVENT_FLAG_CANCELED);
-    window->consumeMotionPointerUp(/*idx=*/1, ui::LogicalDisplayId::DEFAULT,
-                                   AMOTION_EVENT_FLAG_CANCELED);
+    window->consumeMotionPointerUp(/*pointerIdx=*/2,
+                                   AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                         WithFlags(AMOTION_EVENT_FLAG_CANCELED),
+                                         WithPointerCount(3)));
+    window->consumeMotionPointerUp(/*pointerIdx=*/1,
+                                   AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                         WithFlags(AMOTION_EVENT_FLAG_CANCELED),
+                                         WithPointerCount(2)));
 
     spy->assertNoEvents();
     window->assertNoEvents();
@@ -13745,4 +13782,9 @@
                                                 /*pointerId=*/0));
 }
 
+TEST_F(InputDispatcherTest, FocusedDisplayChangeIsNotified) {
+    mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+    mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index fea1349..19bc5be 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -57,16 +57,16 @@
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                     int32_t resolution) {
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _))
-            .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                outAxisInfo->valid = valid;
-                outAxisInfo->minValue = min;
-                outAxisInfo->maxValue = max;
-                outAxisInfo->flat = 0;
-                outAxisInfo->fuzz = 0;
-                outAxisInfo->resolution = resolution;
-                return valid ? OK : -1;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+            .WillRepeatedly(Return(valid ? std::optional<RawAbsoluteAxisInfo>{{
+                                                   .valid = true,
+                                                   .minValue = min,
+                                                   .maxValue = max,
+                                                   .flat = 0,
+                                                   .fuzz = 0,
+                                                   .resolution = resolution,
+                                           }}
+                                         : std::nullopt));
 }
 
 void InputMapperUnitTest::expectScanCodes(bool present, std::set<int> scanCodes) {
@@ -104,7 +104,7 @@
     event.type = type;
     event.code = code;
     event.value = value;
-    return mMapper->process(&event);
+    return mMapper->process(event);
 }
 
 const char* InputMapperTest::DEVICE_NAME = "device";
@@ -195,7 +195,7 @@
     event.type = type;
     event.code = code;
     event.value = value;
-    std::list<NotifyArgs> processArgList = mapper.process(&event);
+    std::list<NotifyArgs> processArgList = mapper.process(event);
     for (const NotifyArgs& args : processArgList) {
         mFakeListener->notify(args);
     }
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 8536ff0..fa9d263 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -309,9 +309,9 @@
         return {};
     }
 
-    std::list<NotifyArgs> process(const RawEvent* rawEvent) override {
+    std::list<NotifyArgs> process(const RawEvent& rawEvent) override {
         std::scoped_lock<std::mutex> lock(mLock);
-        mLastEvent = *rawEvent;
+        mLastEvent = rawEvent;
         mProcessWasCalled = true;
         mStateChangedCondition.notify_all();
         return mProcessResult;
@@ -3314,6 +3314,10 @@
 
 class KeyboardInputMapperTest : public InputMapperTest {
 protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::ALPHAKEY);
+    }
     const std::string UNIQUE_ID = "local:0";
     const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
     void prepareDisplay(ui::Rotation orientation);
@@ -3354,8 +3358,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetSources) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources());
 }
@@ -3370,8 +3373,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -3471,8 +3473,7 @@
     mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // Key down by scan code.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1);
@@ -3493,8 +3494,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
     // Key down
@@ -3516,8 +3516,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3557,8 +3556,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
@@ -3579,8 +3577,7 @@
 
     addConfigurationProperty("keyboard.orientationAware", "1");
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     prepareDisplay(ui::ROTATION_0);
     ASSERT_NO_FATAL_FAILURE(
@@ -3651,8 +3648,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
     // Display id should be LogicalDisplayId::INVALID without any display configuration.
@@ -3677,8 +3673,7 @@
 
     addConfigurationProperty("keyboard.orientationAware", "1");
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
     // Display id should be LogicalDisplayId::INVALID without any display configuration.
@@ -3705,8 +3700,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1);
     ASSERT_EQ(1, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
@@ -3717,8 +3711,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
     ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y))
@@ -3730,8 +3723,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1);
     ASSERT_EQ(1, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
@@ -3742,8 +3734,7 @@
 
 TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
 
@@ -3762,8 +3753,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -3828,8 +3818,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // Meta state should be AMETA_NONE after reset
     std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
@@ -3878,16 +3867,14 @@
     mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
     KeyboardInputMapper& mapper2 =
             device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
                                                                 mFakePolicy
                                                                         ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD,
-                                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                                AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -3949,8 +3936,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -4000,8 +3986,7 @@
             device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
                                                                 mFakePolicy
                                                                         ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD,
-                                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                                AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -4020,11 +4005,9 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     // Suppose we have two mappers. (DPAD + KEYBOARD)
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
-                                               AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -4042,8 +4025,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper1 =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // keyboard 2.
     const std::string USB2 = "USB2";
@@ -4065,8 +4047,7 @@
             device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
                                                                 mFakePolicy
                                                                         ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD,
-                                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                                AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -4122,8 +4103,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Key down by scan code.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
@@ -4148,8 +4128,7 @@
 }
 
 TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                               AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -4180,8 +4159,7 @@
                                     RawLayoutInfo{.languageTag = "en", .layoutType = "extended"});
 
     // Configuration
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                               AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     InputReaderConfiguration config;
     std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
 
@@ -4192,8 +4170,7 @@
 TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) {
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE);
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
     // Key down
@@ -4202,14 +4179,27 @@
     ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags);
 }
 
-// --- KeyboardInputMapperTest_ExternalDevice ---
+// --- KeyboardInputMapperTest_ExternalAlphabeticDevice ---
 
-class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
+class KeyboardInputMapperTest_ExternalAlphabeticDevice : public InputMapperTest {
 protected:
-    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); }
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::ALPHAKEY | InputDeviceClass::EXTERNAL);
+    }
 };
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) {
+// --- KeyboardInputMapperTest_ExternalNonAlphabeticDevice ---
+
+class KeyboardInputMapperTest_ExternalNonAlphabeticDevice : public InputMapperTest {
+protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::EXTERNAL);
+    }
+};
+
+TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, WakeBehavior_AlphabeticKeyboard) {
     // For external devices, keys will trigger wake on key down. Media keys should also trigger
     // wake if triggered from external devices.
 
@@ -4219,8 +4209,7 @@
                           POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
@@ -4248,7 +4237,7 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboard) {
+TEST_F(KeyboardInputMapperTest_ExternalNonAlphabeticDevice, WakeBehavior_NonAlphabeticKeyboard) {
     // For external devices, keys will trigger wake on key down. Media keys should not trigger
     // wake if triggered from external non-alphaebtic keyboard (e.g. headsets).
 
@@ -4257,8 +4246,7 @@
                           POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
     NotifyKeyArgs args;
@@ -4278,7 +4266,7 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) {
+TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, DoNotWakeByDefaultBehavior) {
     // Tv Remote key's wake behavior is prescribed by the keylayout file.
 
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
@@ -4287,8 +4275,7 @@
 
     addConfigurationProperty("keyboard.doNotWakeByDefault", "1");
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
@@ -5485,6 +5472,9 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             x, y, pressure, size, tool, tool, tool, tool, orientation, distance));
     ASSERT_EQ(tilt, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TILT));
+    ASSERT_EQ(args.flags,
+              AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+                      AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION);
 }
 
 TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) {
@@ -6255,7 +6245,7 @@
     SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
@@ -6278,7 +6268,7 @@
                                InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
 
     // Check whether device type update was successful.
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mDevice->getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) {
@@ -7927,6 +7917,7 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor,
             orientation, distance));
+    ASSERT_EQ(args.flags, AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION);
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
@@ -10595,24 +10586,24 @@
     ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR);
 }
 
-TEST_F(LightControllerTest, PlayerIdLight) {
+TEST_F(LightControllerTest, SonyPlayerIdLight) {
     RawLightInfo info1 = {.id = 1,
-                          .name = "player1",
+                          .name = "sony1",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
     RawLightInfo info2 = {.id = 2,
-                          .name = "player2",
+                          .name = "sony2",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
     RawLightInfo info3 = {.id = 3,
-                          .name = "player3",
+                          .name = "sony3",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
     RawLightInfo info4 = {.id = 4,
-                          .name = "player4",
+                          .name = "sony4",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
@@ -10626,6 +10617,49 @@
     controller.populateDeviceInfo(&info);
     std::vector<InputDeviceLightInfo> lights = info.getLights();
     ASSERT_EQ(1U, lights.size());
+    ASSERT_STREQ("sony", lights[0].name.c_str());
+    ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type);
+    ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS));
+    ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB));
+
+    ASSERT_FALSE(controller.setLightColor(lights[0].id, LIGHT_COLOR));
+    ASSERT_TRUE(controller.setLightPlayerId(lights[0].id, LIGHT_PLAYER_ID));
+    ASSERT_EQ(controller.getLightPlayerId(lights[0].id).value_or(-1), LIGHT_PLAYER_ID);
+    ASSERT_STREQ("sony", lights[0].name.c_str());
+}
+
+TEST_F(LightControllerTest, PlayerIdLight) {
+    RawLightInfo info1 = {.id = 1,
+                          .name = "player-1",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info2 = {.id = 2,
+                          .name = "player-2",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info3 = {.id = 3,
+                          .name = "player-3",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info4 = {.id = 4,
+                          .name = "player-4",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    mFakeEventHub->addRawLightInfo(info1.id, std::move(info1));
+    mFakeEventHub->addRawLightInfo(info2.id, std::move(info2));
+    mFakeEventHub->addRawLightInfo(info3.id, std::move(info3));
+    mFakeEventHub->addRawLightInfo(info4.id, std::move(info4));
+
+    PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    std::vector<InputDeviceLightInfo> lights = info.getLights();
+    ASSERT_EQ(1U, lights.size());
+    ASSERT_STREQ("player", lights[0].name.c_str());
     ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type);
     ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS));
     ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB));
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
index 32acb5f..db4e761 100644
--- a/services/inputflinger/tests/InputTraceSession.cpp
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -20,6 +20,9 @@
 #include <android-base/logging.h>
 #include <gtest/gtest.h>
 #include <input/PrintTools.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions_impl.pbzero.h>
 
 #include <utility>
 
@@ -30,6 +33,8 @@
 using perfetto::protos::pbzero::AndroidKeyEvent;
 using perfetto::protos::pbzero::AndroidMotionEvent;
 using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+using perfetto::protos::pbzero::WinscopeExtensions;
+using perfetto::protos::pbzero::WinscopeExtensionsImpl;
 
 // These operator<< definitions must be in the global namespace for them to be accessible to the
 // GTEST library. They cannot be in the anonymous namespace.
@@ -85,38 +90,50 @@
 
     Trace::Decoder trace{rawTrace};
     if (trace.has_packet()) {
-        auto it = trace.packet();
-        while (it) {
+        for (auto it = trace.packet(); it; it++) {
             TracePacket::Decoder packet{it->as_bytes()};
-            if (packet.has_android_input_event()) {
-                AndroidInputEvent::Decoder event{packet.android_input_event()};
-                if (event.has_dispatcher_motion_event()) {
-                    tracedMotions.emplace_back(event.dispatcher_motion_event(),
-                                               /*redacted=*/false);
-                }
-                if (event.has_dispatcher_motion_event_redacted()) {
-                    tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
-                                               /*redacted=*/true);
-                }
-                if (event.has_dispatcher_key_event()) {
-                    tracedKeys.emplace_back(event.dispatcher_key_event(),
-                                            /*redacted=*/false);
-                }
-                if (event.has_dispatcher_key_event_redacted()) {
-                    tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
-                                            /*redacted=*/true);
-                }
-                if (event.has_dispatcher_window_dispatch_event()) {
-                    tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
-                                                        /*redacted=*/false);
-                }
-                if (event.has_dispatcher_window_dispatch_event_redacted()) {
-                    tracedWindowDispatches
-                            .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
-                                          /*redacted=*/true);
-                }
+            if (!packet.has_winscope_extensions()) {
+                continue;
             }
-            it++;
+
+            WinscopeExtensions::Decoder extensions{packet.winscope_extensions()};
+            const auto& field =
+                    extensions.Get(WinscopeExtensionsImpl::kAndroidInputEventFieldNumber);
+            if (!field.valid()) {
+                continue;
+            }
+
+            EXPECT_TRUE(packet.has_timestamp());
+            EXPECT_TRUE(packet.has_timestamp_clock_id());
+            EXPECT_EQ(packet.timestamp_clock_id(),
+                      static_cast<uint32_t>(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC));
+
+            AndroidInputEvent::Decoder event{field.as_bytes()};
+            if (event.has_dispatcher_motion_event()) {
+                tracedMotions.emplace_back(event.dispatcher_motion_event(),
+                                           /*redacted=*/false);
+            }
+            if (event.has_dispatcher_motion_event_redacted()) {
+                tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+                                           /*redacted=*/true);
+            }
+            if (event.has_dispatcher_key_event()) {
+                tracedKeys.emplace_back(event.dispatcher_key_event(),
+                                        /*redacted=*/false);
+            }
+            if (event.has_dispatcher_key_event_redacted()) {
+                tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+                                        /*redacted=*/true);
+            }
+            if (event.has_dispatcher_window_dispatch_event()) {
+                tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+                                                    /*redacted=*/false);
+            }
+            if (event.has_dispatcher_window_dispatch_event_redacted()) {
+                tracedWindowDispatches
+                        .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+                                      /*redacted=*/true);
+            }
         }
     }
     return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
index ed20bc8..bda5521 100644
--- a/services/inputflinger/tests/InputTraceSession.h
+++ b/services/inputflinger/tests/InputTraceSession.h
@@ -22,7 +22,6 @@
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <perfetto/config/android/android_input_event_config.pbzero.h>
-#include <perfetto/trace/android/android_input_event.pbzero.h>
 #include <perfetto/trace/trace.pbzero.h>
 #include <perfetto/tracing.h>
 #include <variant>
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
index 617d67f..2ccd93e 100644
--- a/services/inputflinger/tests/InputTracingTest.cpp
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -30,6 +30,8 @@
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions_impl.pbzero.h>
 #include <perfetto/trace/trace.pbzero.h>
 #include <private/android_filesystem_config.h>
 #include <map>
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 6389cdc..bacc6d4 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -27,7 +27,9 @@
 
 #include <EventHub.h>
 #include <InputReaderBase.h>
+#include <InputReaderContext.h>
 #include <NotifyArgs.h>
+#include <PointerChoreographerPolicyInterface.h>
 #include <StylusState.h>
 #include <VibrationElement.h>
 #include <android-base/logging.h>
@@ -36,6 +38,7 @@
 #include <input/InputDevice.h>
 #include <input/KeyCharacterMap.h>
 #include <input/KeyLayoutMap.h>
+#include <input/KeyboardClassifier.h>
 #include <input/PropertyMap.h>
 #include <input/TouchVideoFrame.h>
 #include <input/VirtualKeyMap.h>
@@ -75,8 +78,11 @@
     MOCK_METHOD(void, setLastKeyDownTimestamp, (nsecs_t when));
     MOCK_METHOD(nsecs_t, getLastKeyDownTimestamp, ());
 
+    KeyboardClassifier& getKeyboardClassifier() override { return *mClassifier; };
+
 private:
     int32_t mGeneration = 0;
+    std::unique_ptr<KeyboardClassifier> mClassifier = std::make_unique<KeyboardClassifier>();
 };
 
 class MockEventHubInterface : public EventHubInterface {
@@ -85,8 +91,8 @@
     MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const));
     MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const));
     MOCK_METHOD(std::optional<PropertyMap>, getConfiguration, (int32_t deviceId), (const));
-    MOCK_METHOD(status_t, getAbsoluteAxisInfo,
-                (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const));
+    MOCK_METHOD(std::optional<RawAbsoluteAxisInfo>, getAbsoluteAxisInfo,
+                (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const));
     MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const));
@@ -125,7 +131,7 @@
     MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override));
     MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override));
 
-    MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
+    MOCK_METHOD(std::optional<int32_t>, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis),
                 (const, override));
     MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues,
                 (int32_t deviceId, int32_t axis, size_t slotCount), (const, override));
@@ -174,4 +180,13 @@
     MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override));
 };
 
+class MockPointerChoreographerPolicyInterface : public PointerChoreographerPolicyInterface {
+public:
+    MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, createPointerController,
+                (PointerControllerInterface::ControllerType), (override));
+    MOCK_METHOD(void, notifyPointerDisplayIdChanged,
+                (ui::LogicalDisplayId displayId, const FloatPoint& position), (override));
+    MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
+};
+
 } // namespace android
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 031b77d..ab47cc6 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -67,17 +67,7 @@
         EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get()));
 
         mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
-                                                         AINPUT_SOURCE_KEYBOARD,
-                                                         AINPUT_KEYBOARD_TYPE_ALPHABETIC);
-    }
-
-    void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
-        for (int32_t keyCode : keyCodes) {
-            process(EV_KEY, keyCode, 1);
-            process(EV_SYN, SYN_REPORT, 0);
-            process(EV_KEY, keyCode, 0);
-            process(EV_SYN, SYN_REPORT, 0);
-        }
+                                                         AINPUT_SOURCE_KEYBOARD);
     }
 
     void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
@@ -96,42 +86,6 @@
 };
 
 /**
- * Pointer visibility should remain unaffected if there is no active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDoesNotHidePointer) {
-    testPointerVisibilityForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectVisible= */ true);
-}
-
-/**
- * Pointer should hide if there is a active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionHidePointer) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
-}
-
-/**
- * Pointer should still hide if touchpad taps are already disabled
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
-    testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
-}
-
-/**
- * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
- * active
- */
-TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDoesNotHidePointer) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    std::vector<int32_t> metaKeys{KEY_LEFTALT,   KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
-                                  KEY_FN,        KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA,
-                                  KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK,   KEY_SCROLLLOCK};
-    testPointerVisibilityForKeys(metaKeys, /* expectVisible= */ true);
-}
-
-/**
  * Touchpad tap should not be disabled if there is no active Input Method Connection
  */
 TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDontDisableTouchpadTap) {
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index b5f8971..d4d3c38 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -99,11 +99,8 @@
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
         // reset current slot at the beginning
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-                .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
 
         // mark all slots not in use
         mockSlotValues({});
@@ -211,11 +208,8 @@
     const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
 
     // On buffer overflow mapper will be reset and MT slots data will be repopulated
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-            .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
-                *outValue = 1;
-                return OK;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+            .WillRepeatedly(Return(1));
 
     mockSlotValues(
             {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
index 5e67506..b441a23 100644
--- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
+++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
@@ -38,7 +38,7 @@
         event.type = type;
         event.code = code;
         event.value = value;
-        mMotionAccumulator.process(&event);
+        mMotionAccumulator.process(event);
     }
 };
 
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 69a7382..9a5b6a7 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "FakePointerController.h"
+#include "InterfaceMocks.h"
 #include "NotifyArgsBuilders.h"
 #include "TestEventMatchers.h"
 #include "TestInputListener.h"
@@ -89,10 +90,55 @@
 
 // --- PointerChoreographerTest ---
 
-class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface {
+class TestPointerChoreographer : public PointerChoreographer {
+public:
+    TestPointerChoreographer(InputListenerInterface& inputListener,
+                             PointerChoreographerPolicyInterface& policy,
+                             sp<gui::WindowInfosListener>& windowInfoListener,
+                             const std::vector<gui::WindowInfo>& mInitialWindowInfos);
+};
+
+TestPointerChoreographer::TestPointerChoreographer(
+        InputListenerInterface& inputListener, PointerChoreographerPolicyInterface& policy,
+        sp<gui::WindowInfosListener>& windowInfoListener,
+        const std::vector<gui::WindowInfo>& mInitialWindowInfos)
+      : PointerChoreographer(
+                inputListener, policy,
+                [&windowInfoListener,
+                 &mInitialWindowInfos](const sp<android::gui::WindowInfosListener>& listener) {
+                    windowInfoListener = listener;
+                    return mInitialWindowInfos;
+                },
+                [&windowInfoListener](const sp<android::gui::WindowInfosListener>& listener) {
+                    windowInfoListener = nullptr;
+                }) {}
+
+class PointerChoreographerTest : public testing::Test {
 protected:
     TestInputListener mTestListener;
-    PointerChoreographer mChoreographer{mTestListener, *this};
+    sp<gui::WindowInfosListener> mRegisteredWindowInfoListener;
+    std::vector<gui::WindowInfo> mInjectedInitialWindowInfos;
+    testing::NiceMock<MockPointerChoreographerPolicyInterface> mMockPolicy;
+    TestPointerChoreographer mChoreographer{mTestListener, mMockPolicy,
+                                            mRegisteredWindowInfoListener,
+                                            mInjectedInitialWindowInfos};
+
+    void SetUp() override {
+        // flag overrides
+        input_flags::hide_pointer_indicators_for_secure_windows(true);
+
+        ON_CALL(mMockPolicy, createPointerController).WillByDefault([this](ControllerType type) {
+            std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+            EXPECT_FALSE(pc->isPointerShown());
+            mCreatedControllers.emplace_back(type, pc);
+            return pc;
+        });
+
+        ON_CALL(mMockPolicy, notifyPointerDisplayIdChanged)
+                .WillByDefault([this](ui::LogicalDisplayId displayId, const FloatPoint& position) {
+                    mPointerDisplayIdNotified = displayId;
+                });
+    }
 
     std::shared_ptr<FakePointerController> assertPointerControllerCreated(
             ControllerType expectedType) {
@@ -131,23 +177,20 @@
 
     void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); }
 
+    void assertWindowInfosListenerRegistered() {
+        ASSERT_NE(nullptr, mRegisteredWindowInfoListener)
+                << "WindowInfosListener was not registered";
+    }
+
+    void assertWindowInfosListenerNotRegistered() {
+        ASSERT_EQ(nullptr, mRegisteredWindowInfoListener)
+                << "WindowInfosListener was not unregistered";
+    }
+
 private:
     std::deque<std::pair<ControllerType, std::shared_ptr<FakePointerController>>>
             mCreatedControllers;
     std::optional<ui::LogicalDisplayId> mPointerDisplayIdNotified;
-
-    std::shared_ptr<PointerControllerInterface> createPointerController(
-            ControllerType type) override {
-        std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
-        EXPECT_FALSE(pc->isPointerShown());
-        mCreatedControllers.emplace_back(type, pc);
-        return pc;
-    }
-
-    void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
-                                       const FloatPoint& position) override {
-        mPointerDisplayIdNotified = displayId;
-    }
 };
 
 TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
@@ -1636,16 +1679,36 @@
     firstMousePc->assertPointerIconNotSet();
 }
 
-using HidePointerForPrivacySensitiveDisplaysFixtureParam =
+using SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam =
         std::tuple<std::string_view /*name*/, uint32_t /*source*/, ControllerType, PointerBuilder,
                    std::function<void(PointerChoreographer&)>, int32_t /*action*/>;
 
-class HidePointerForPrivacySensitiveDisplaysTestFixture
+class SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture
       : public PointerChoreographerTest,
-        public ::testing::WithParamInterface<HidePointerForPrivacySensitiveDisplaysFixtureParam> {};
+        public ::testing::WithParamInterface<
+                SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam> {
+protected:
+    void initializePointerDevice(const PointerBuilder& pointerBuilder, const uint32_t source,
+                                 const std::function<void(PointerChoreographer&)> onControllerInit,
+                                 const int32_t action) {
+        mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+        // Add appropriate pointer device
+        mChoreographer.notifyInputDevicesChanged(
+                {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+        onControllerInit(mChoreographer);
+
+        // Emit input events to create PointerController
+        mChoreographer.notifyMotion(MotionArgsBuilder(action, source)
+                                            .pointer(pointerBuilder)
+                                            .deviceId(DEVICE_ID)
+                                            .displayId(DISPLAY_ID)
+                                            .build());
+    }
+};
 
 INSTANTIATE_TEST_SUITE_P(
-        PointerChoreographerTest, HidePointerForPrivacySensitiveDisplaysTestFixture,
+        PointerChoreographerTest, SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
         ::testing::Values(
                 std::make_tuple(
                         "TouchSpots", AINPUT_SOURCE_TOUCHSCREEN, ControllerType::TOUCH,
@@ -1663,44 +1726,205 @@
                         "DrawingTablet", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS,
                         ControllerType::MOUSE, STYLUS_POINTER, [](PointerChoreographer& pc) {},
                         AMOTION_EVENT_ACTION_HOVER_ENTER)),
-        [](const testing::TestParamInfo<HidePointerForPrivacySensitiveDisplaysFixtureParam>& p) {
+        [](const testing::TestParamInfo<
+                SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam>& p) {
             return std::string{std::get<0>(p.param)};
         });
 
-TEST_P(HidePointerForPrivacySensitiveDisplaysTestFixture,
-       HidesPointerOnMirroredDisplaysForPrivacySensitiveDisplay) {
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       WindowInfosListenerIsOnlyRegisteredWhenRequired) {
     const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
             GetParam();
-    input_flags::hide_pointer_indicators_for_secure_windows(true);
+    assertWindowInfosListenerNotRegistered();
+
+    // Listener should registered when a pointer device is added
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+    assertWindowInfosListenerRegistered();
+
+    mChoreographer.notifyInputDevicesChanged({});
+    assertWindowInfosListenerNotRegistered();
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       InitialDisplayInfoIsPopulatedForListener) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    // listener should not be registered if there is no pointer device
+    assertWindowInfosListenerNotRegistered();
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    mInjectedInitialWindowInfos = {windowInfo};
+
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+    assertWindowInfosListenerRegistered();
+
+    // Pointer indicators should be hidden based on the initial display info
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // un-marking the privacy sensitive display should reset the state
+    windowInfo.inputConfig.clear();
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       SkipsPointerScreenshotForPrivacySensitiveWindows) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+
+    // By default pointer indicators should not be hidden
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // marking a display privacy sensitive should set flag to hide pointer indicators on the
+    // display screenshot
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // un-marking the privacy sensitive display should reset the state
+    windowInfo.inputConfig.clear();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       DoesNotSkipPointerScreenshotForHiddenPrivacySensitiveWindows) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+
+    // By default pointer indicators should not be hidden
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       DoesNotUpdateControllerForUnchangedPrivacySensitiveWindows) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+
+    auto pc = assertPointerControllerCreated(controllerType);
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    gui::WindowInfo windowInfo2 = windowInfo;
+    windowInfo2.inputConfig.clear();
+    pc->assertSkipScreenshotFlagChanged();
+
+    // controller should not be updated if there are no changes in privacy sensitive windows
+    mRegisteredWindowInfoListener->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                                        {{windowInfo, windowInfo2},
+                                                         {displayInfo},
+                                                         /*vsyncId=*/0,
+                                                         /*timestamp=*/0});
+    pc->assertSkipScreenshotFlagNotChanged();
+}
+
+TEST_F_WITH_FLAGS(
+        PointerChoreographerTest, HidesPointerScreenshotForExistingPrivacySensitiveWindows,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            hide_pointer_indicators_for_secure_windows))) {
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
-    // Add appropriate pointer device
+    // Add a first mouse device
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
-    onControllerInit(mChoreographer);
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
 
-    // Emit input events to create PointerController
-    mChoreographer.notifyMotion(MotionArgsBuilder(action, source)
-                                        .pointer(pointerBuilder)
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .pointer(MOUSE_POINTER)
                                         .deviceId(DEVICE_ID)
                                         .displayId(DISPLAY_ID)
                                         .build());
 
-    // By default pointer indicators should not be hidden
-    auto pc = assertPointerControllerCreated(controllerType);
-    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
-    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
 
-    // marking a display privacy sensitive should set flag to hide pointer indicators on the
-    // corresponding mirrored display
-    mChoreographer.onPrivacySensitiveDisplaysChanged(/*privacySensitiveDisplays=*/{DISPLAY_ID});
-    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/true);
-    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // Add a second touch device and controller
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Pointer indicators should be hidden for this controller by default
+    auto pc2 = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
 
     // un-marking the privacy sensitive display should reset the state
-    mChoreographer.onPrivacySensitiveDisplaysChanged(/*privacySensitiveDisplays=*/{});
-    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
-    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+    windowInfo.inputConfig.clear();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+    pc2->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc2->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
 }
 
 TEST_P(StylusTestFixture, SetsPointerIconForStylus) {
@@ -2070,4 +2294,220 @@
     assertPointerControllerRemoved(pc);
 }
 
+class PointerVisibilityOnKeyPressTest : public PointerChoreographerTest {
+protected:
+    const std::unordered_map<int32_t, int32_t>
+            mMetaKeyStates{{AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON},
+                           {AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON},
+                           {AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON},
+                           {AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON},
+                           {AKEYCODE_SYM, AMETA_SYM_ON},
+                           {AKEYCODE_FUNCTION, AMETA_FUNCTION_ON},
+                           {AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON},
+                           {AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON},
+                           {AKEYCODE_META_LEFT, AMETA_META_LEFT_ON},
+                           {AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON},
+                           {AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON},
+                           {AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON},
+                           {AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON}};
+
+    void notifyKey(ui::LogicalDisplayId targetDisplay, int32_t keyCode,
+                   int32_t metaState = AMETA_NONE) {
+        if (metaState == AMETA_NONE && mMetaKeyStates.contains(keyCode)) {
+            // For simplicity, we always set the corresponding meta state when sending a meta
+            // keycode. This does not take into consideration when the meta state is updated in
+            // reality.
+            metaState = mMetaKeyStates.at(keyCode);
+        }
+        mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                         .displayId(targetDisplay)
+                                         .keyCode(keyCode)
+                                         .metaState(metaState)
+                                         .build());
+        mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                                         .displayId(targetDisplay)
+                                         .keyCode(keyCode)
+                                         .metaState(metaState)
+                                         .build());
+    }
+
+    void metaKeyCombinationHidesPointer(FakePointerController& pc, int32_t keyCode,
+                                        int32_t metaKeyCode) {
+        ASSERT_TRUE(pc.isPointerShown());
+        notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode));
+        ASSERT_FALSE(pc.isPointerShown());
+
+        unfadePointer();
+    }
+
+    void metaKeyCombinationDoesNotHidePointer(FakePointerController& pc, int32_t keyCode,
+                                              int32_t metaKeyCode) {
+        ASSERT_TRUE(pc.isPointerShown());
+        notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode));
+        ASSERT_TRUE(pc.isPointerShown());
+    }
+
+    void unfadePointer() {
+        // unfade pointer by injecting mose hover event
+        mChoreographer.notifyMotion(
+                MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                        .pointer(MOUSE_POINTER)
+                        .deviceId(DEVICE_ID)
+                        .displayId(DISPLAY_ID)
+                        .build());
+    }
+};
+
+TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutImeConnectionDoesNotHidePointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0);
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A);
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_CTRL_LEFT);
+
+    ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, AlphanumericKeystrokesWithImeConnectionHidePointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+    notifyKey(DISPLAY_ID, AKEYCODE_0);
+    ASSERT_FALSE(pc->isPointerShown());
+
+    unfadePointer();
+
+    notifyKey(DISPLAY_ID, AKEYCODE_A);
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, MetaKeystrokesDoNotHidePointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+    const std::vector<int32_t> metaKeyCodes{AKEYCODE_ALT_LEFT,   AKEYCODE_ALT_RIGHT,
+                                            AKEYCODE_SHIFT_LEFT, AKEYCODE_SHIFT_RIGHT,
+                                            AKEYCODE_SYM,        AKEYCODE_FUNCTION,
+                                            AKEYCODE_CTRL_LEFT,  AKEYCODE_CTRL_RIGHT,
+                                            AKEYCODE_META_LEFT,  AKEYCODE_META_RIGHT,
+                                            AKEYCODE_CAPS_LOCK,  AKEYCODE_NUM_LOCK,
+                                            AKEYCODE_SCROLL_LOCK};
+    for (int32_t keyCode : metaKeyCodes) {
+        notifyKey(ui::LogicalDisplayId::INVALID, keyCode);
+    }
+
+    ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutTargetHidePointerOnlyOnFocusedDisplay) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setFocusedDisplay(DISPLAY_ID);
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    auto pc1 = assertPointerControllerCreated(ControllerType::MOUSE);
+    auto pc2 = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc1->isPointerShown());
+    ASSERT_TRUE(pc2->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0);
+    ASSERT_FALSE(pc1->isPointerShown());
+    ASSERT_TRUE(pc2->isPointerShown());
+    unfadePointer();
+
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A);
+    ASSERT_FALSE(pc1->isPointerShown());
+    ASSERT_TRUE(pc2->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, TestMetaKeyCombinations) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+    // meta key combinations that should hide pointer
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_LEFT);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_RIGHT);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_CAPS_LOCK);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_0, AKEYCODE_NUM_LOCK);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SCROLL_LOCK);
+
+    // meta key combinations that should not hide pointer
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_LEFT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_RIGHT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_LEFT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_RIGHT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_SYM);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_FUNCTION);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_LEFT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_RIGHT);
+}
+
+class PointerChoreographerWindowInfoListenerTest : public testing::Test {};
+
+TEST_F_WITH_FLAGS(
+        PointerChoreographerWindowInfoListenerTest,
+        doesNotCrashIfListenerCalledAfterPointerChoreographerDestroyed,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            hide_pointer_indicators_for_secure_windows))) {
+    sp<android::gui::WindowInfosListener> registeredListener;
+    sp<android::gui::WindowInfosListener> localListenerCopy;
+    {
+        testing::NiceMock<MockPointerChoreographerPolicyInterface> mockPolicy;
+        EXPECT_CALL(mockPolicy, createPointerController(ControllerType::MOUSE))
+                .WillOnce(testing::Return(std::make_shared<FakePointerController>()));
+        TestInputListener testListener;
+        std::vector<gui::WindowInfo> injectedInitialWindowInfos;
+        TestPointerChoreographer testChoreographer{testListener, mockPolicy, registeredListener,
+                                                   injectedInitialWindowInfos};
+        testChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+        // Add mouse to create controller and listener
+        testChoreographer.notifyInputDevicesChanged(
+                {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+
+        ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
+        localListenerCopy = registeredListener;
+    }
+    ASSERT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered";
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    localListenerCopy->onWindowInfosChanged(
+            /*windowInfosUpdate=*/{{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h
index ad48b0f..082bbb8 100644
--- a/services/inputflinger/tests/TestConstants.h
+++ b/services/inputflinger/tests/TestConstants.h
@@ -24,6 +24,9 @@
 
 using std::chrono_literals::operator""ms;
 
+// Timeout for waiting for an input device to be added and processed
+static constexpr std::chrono::duration ADD_INPUT_DEVICE_TIMEOUT = 500ms;
+
 // Timeout for waiting for an expected event
 static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
 
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 2b62dd1..1afb4f0 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -103,11 +103,8 @@
         setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
-                .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
         EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_))
                 .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> {
                     return base::ResultError("Axis not supported", NAME_NOT_FOUND);
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index af20a27..8361517 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -81,7 +81,7 @@
                             mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                InputReaderConfiguration::Change(0));
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    unused += mapper.process(&rawEvent);
+                    unused += mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index 922cbdf..9e02502 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -50,11 +50,10 @@
     FuzzInputReaderContext context(eventHub, fdp);
     InputDevice device = getFuzzedInputDevice(*fdp, &context);
 
-    KeyboardInputMapper& mapper = getMapperForDevice<
-            ThreadSafeFuzzedDataProvider,
-            KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{},
-                                 /*source=*/fdp->ConsumeIntegral<uint32_t>(),
-                                 /*keyboardType=*/fdp->ConsumeIntegral<int32_t>());
+    KeyboardInputMapper& mapper =
+            getMapperForDevice<ThreadSafeFuzzedDataProvider,
+                               KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{},
+                                                    /*source=*/fdp->ConsumeIntegral<uint32_t>());
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
@@ -80,7 +79,7 @@
                 },
                 [&]() -> void {
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+                    std::list<NotifyArgs> unused = mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     mapper.getKeyCodeState(fdp->ConsumeIntegral<uint32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 25f2f2e..6dea540 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -17,6 +17,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 
 #include <EventHub.h>
 #include <InputDevice.h>
@@ -119,16 +120,26 @@
     void setAbsoluteAxisInfo(int32_t deviceId, int axis, const RawAbsoluteAxisInfo& axisInfo) {
         mAxes[deviceId][axis] = axisInfo;
     }
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override {
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override {
         if (auto deviceAxesIt = mAxes.find(deviceId); deviceAxesIt != mAxes.end()) {
             const std::map<int, RawAbsoluteAxisInfo>& deviceAxes = deviceAxesIt->second;
             if (auto axisInfoIt = deviceAxes.find(axis); axisInfoIt != deviceAxes.end()) {
-                *outAxisInfo = axisInfoIt->second;
-                return OK;
+                return axisInfoIt->second;
             }
         }
-        return mFdp->ConsumeIntegral<status_t>();
+        if (mFdp->ConsumeBool()) {
+            return std::optional<RawAbsoluteAxisInfo>({
+                    .valid = mFdp->ConsumeBool(),
+                    .minValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .maxValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .flat = mFdp->ConsumeIntegral<int32_t>(),
+                    .fuzz = mFdp->ConsumeIntegral<int32_t>(),
+                    .resolution = mFdp->ConsumeIntegral<int32_t>(),
+            });
+        } else {
+            return std::nullopt;
+        }
     }
     bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); }
     bool hasInputProperty(int32_t deviceId, int property) const override {
@@ -197,9 +208,12 @@
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override {
-        return mFdp->ConsumeIntegral<status_t>();
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override {
+        if (mFdp->ConsumeBool()) {
+            return mFdp->ConsumeIntegral<int32_t>();
+        } else {
+            return std::nullopt;
+        }
     }
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override {
@@ -338,9 +352,11 @@
 
     void setLastKeyDownTimestamp(nsecs_t when) { mLastKeyDownTimestamp = when; };
     nsecs_t getLastKeyDownTimestamp() { return mLastKeyDownTimestamp; };
+    KeyboardClassifier& getKeyboardClassifier() override { return *mClassifier; }
 
 private:
     nsecs_t mLastKeyDownTimestamp;
+    std::unique_ptr<KeyboardClassifier> mClassifier = std::make_unique<KeyboardClassifier>();
 };
 
 template <class Fdp>
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index d3f6690..f29577d 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -100,7 +100,7 @@
                 },
                 [&]() -> void {
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+                    std::list<NotifyArgs> unused = mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     mapper.getKeyCodeState(fdp->ConsumeIntegral<uint32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
index ac2030a..a42d447 100644
--- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -44,7 +44,7 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+                    std::list<NotifyArgs> unused = mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     mapper.getSwitchState(fdp->ConsumeIntegral<uint32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index 643e8b9..c620032 100644
--- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -176,7 +176,7 @@
                 },
                 [&]() -> void {
                     RawEvent event = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&event);
+                    std::list<NotifyArgs> unused = mapper.process(event);
                 },
         })();
     }
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index afaf0ae..f4b0265 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -84,6 +84,7 @@
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
 
     static_libs: [
diff --git a/services/sensorservice/OWNERS b/services/sensorservice/OWNERS
index 90c2330..7347ac7 100644
--- a/services/sensorservice/OWNERS
+++ b/services/sensorservice/OWNERS
@@ -1,3 +1 @@
-arthuri@google.com
 bduddie@google.com
-stange@google.com
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index f6f104e..b2dc89b 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -26,6 +26,11 @@
         "libfakeservicemanager",
         "libcutils",
         "liblog",
+        "libsensor_flags_c_lib",
+    ],
+    shared_libs: [
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
     srcs: [
         "fuzzer.cpp",
diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig
index 5b499a8..7abfbaa 100644
--- a/services/sensorservice/senserservice_flags.aconfig
+++ b/services/sensorservice/senserservice_flags.aconfig
@@ -27,4 +27,4 @@
   namespace: "sensors"
   description: "When this flag is enabled, sensor service will only erase dynamic sensor data at the end of the threadLoop to prevent race condition."
   bug: "329020894"
-}
\ No newline at end of file
+}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 8ca796e..1b6c598 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -85,6 +85,7 @@
         "libui",
         "libutils",
         "libSurfaceFlingerProp",
+        "libaconfig_storage_read_api_cc"
     ],
     static_libs: [
         "iinputflinger_aidl_lib_static",
@@ -98,6 +99,7 @@
         "libscheduler",
         "libserviceutils",
         "libshaders",
+        "libsurfaceflingerflags",
         "libtimestats",
         "libtonemap",
     ],
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index d420838..191d475 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -306,7 +306,7 @@
     virtual void finishFrame(GpuCompositionResult&&) = 0;
     virtual std::optional<base::unique_fd> composeSurfaces(
             const Region&, std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&) = 0;
-    virtual void presentFrameAndReleaseLayers() = 0;
+    virtual void presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) = 0;
     virtual void renderCachedSets(const CompositionRefreshArgs&) = 0;
     virtual bool chooseCompositionStrategy(
             std::optional<android::HWComposer::DeviceRequestedChanges>*) = 0;
@@ -314,6 +314,7 @@
             const std::optional<android::HWComposer::DeviceRequestedChanges>& changes) = 0;
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentFrame() = 0;
+    virtual void executeCommands() = 0;
     virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
             bool supportsProtectedContent, ui::Dataspace outputDataspace,
             std::vector<LayerFE*> &outLayerRef) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index d87968f..d1eff24 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -60,6 +60,7 @@
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override;
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentFrame() override;
+    void executeCommands() override;
     void setExpensiveRenderingExpected(bool) override;
     void finishFrame(GpuCompositionResult&&) override;
     bool supportsOffloadPresent() const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index adcbbb9..9990a74 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -104,7 +104,7 @@
     std::optional<base::unique_fd> composeSurfaces(const Region&,
                                                    std::shared_ptr<renderengine::ExternalTexture>,
                                                    base::unique_fd&) override;
-    void presentFrameAndReleaseLayers() override;
+    void presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) override;
     void renderCachedSets(const CompositionRefreshArgs&) override;
     void cacheClientCompositionRequests(uint32_t) override;
     bool canPredictCompositionStrategy(const CompositionRefreshArgs&) override;
@@ -123,7 +123,8 @@
     virtual std::future<bool> chooseCompositionStrategyAsync(
             std::optional<android::HWComposer::DeviceRequestedChanges>*);
     virtual void resetCompositionStrategy();
-    virtual ftl::Future<std::monostate> presentFrameAndReleaseLayersAsync();
+    virtual ftl::Future<std::monostate> presentFrameAndReleaseLayersAsync(
+            bool flushEvenWhenDisabled);
 
 protected:
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
@@ -137,6 +138,7 @@
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentFrame() override;
+    void executeCommands() override {}
     virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings(
             const std::shared_ptr<renderengine::ExternalTexture>& buffer) const;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 3f3deae..d5bf2b5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -121,9 +121,10 @@
                                                 base::unique_fd&));
     MOCK_CONST_METHOD0(getSkipColorTransform, bool());
 
-    MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+    MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled));
     MOCK_METHOD1(renderCachedSets, void(const CompositionRefreshArgs&));
     MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
+    MOCK_METHOD(void, executeCommands, ());
 
     MOCK_METHOD3(generateClientCompositionRequests,
                  std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<compositionengine::LayerFE*>&));
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 83b1b68..c1617d7 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -361,6 +361,15 @@
             static_cast<ui::PixelFormat>(clientTargetProperty.clientTargetProperty.pixelFormat));
 }
 
+void Display::executeCommands() {
+    const auto halDisplayIdOpt = HalDisplayId::tryCast(mId);
+    if (mIsDisconnected || !halDisplayIdOpt) {
+        return;
+    }
+
+    getCompositionEngine().getHwComposer().executeCommands(*halDisplayIdOpt);
+}
+
 compositionengine::Output::FrameFences Display::presentFrame() {
     auto fences = impl::Output::presentFrame();
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 84f3f25..b40aea4 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -479,8 +479,9 @@
     devOptRepaintFlash(refreshArgs);
     finishFrame(std::move(result));
     ftl::Future<std::monostate> future;
+    const bool flushEvenWhenDisabled = !refreshArgs.bufferIdsToUncache.empty();
     if (mOffloadPresent) {
-        future = presentFrameAndReleaseLayersAsync();
+        future = presentFrameAndReleaseLayersAsync(flushEvenWhenDisabled);
 
         // Only offload for this frame. The next frame will determine whether it
         // needs to be offloaded. Leave the HwcAsyncWorker in place. For one thing,
@@ -488,7 +489,7 @@
         // we don't want to churn.
         mOffloadPresent = false;
     } else {
-        presentFrameAndReleaseLayers();
+        presentFrameAndReleaseLayers(flushEvenWhenDisabled);
         future = ftl::yield<std::monostate>({});
     }
     renderCachedSets(refreshArgs);
@@ -1100,9 +1101,9 @@
     finishPrepareFrame();
 }
 
-ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync() {
-    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
-               presentFrameAndReleaseLayers();
+ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) {
+    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([this, flushEvenWhenDisabled]() {
+               presentFrameAndReleaseLayers(flushEvenWhenDisabled);
                return true;
            })))
             .then([](bool) { return std::monostate{}; });
@@ -1177,7 +1178,8 @@
         }
     }
 
-    presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 
     std::this_thread::sleep_for(*refreshArgs.devOptFlashDirtyRegionsDelay);
 
@@ -1567,11 +1569,16 @@
     return false;
 }
 
-void Output::presentFrameAndReleaseLayers() {
+void Output::presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) {
     ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
+        if (flushEvenWhenDisabled && FlagManager::getInstance().flush_buffer_slots_to_uncache()) {
+            // Some commands, like clearing buffer slots, should still be executed
+            // even if the display is not enabled.
+            executeCommands();
+        }
         return;
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index a95a5c6..39163ea 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -1067,8 +1067,8 @@
 
     EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _));
     EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
-
-    mDisplay->presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mDisplay->presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 4612117..629d9f2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -63,6 +63,7 @@
                 (override));
     MOCK_METHOD2(presentAndGetReleaseFences,
                  status_t(HalDisplayId, std::optional<std::chrono::steady_clock::time_point>));
+    MOCK_METHOD(status_t, executeCommands, (HalDisplayId));
     MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode));
     MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t));
     MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 0dc3c9f..c34168d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2014,7 +2014,7 @@
         MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult());
         MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&));
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override));
         MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
         MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
@@ -2046,7 +2046,7 @@
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(_));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(false));
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
     mOutput.present(args);
@@ -2067,7 +2067,7 @@
     EXPECT_CALL(mOutput, prepareFrameAsync());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(_));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(false));
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
     mOutput.present(args);
@@ -2913,7 +2913,7 @@
                      std::optional<base::unique_fd>(const Region&,
                                                     std::shared_ptr<renderengine::ExternalTexture>,
                                                     base::unique_fd&));
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled));
         MOCK_METHOD0(prepareFrame, void());
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
@@ -2950,7 +2950,8 @@
     mOutput.mState.isEnabled = false;
 
     InSequence seq;
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2962,7 +2963,8 @@
 
     InSequence seq;
     EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kEmptyRegion));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2978,7 +2980,8 @@
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _));
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2996,7 +2999,7 @@
                      std::optional<base::unique_fd>(const Region&,
                                                     std::shared_ptr<renderengine::ExternalTexture>,
                                                     base::unique_fd&));
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override));
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
                      bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
@@ -3139,7 +3142,8 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
+        MOCK_METHOD(compositionengine::Output::FrameFences, presentFrame, ());
+        MOCK_METHOD(void, executeCommands, ());
     };
 
     struct Layer {
@@ -3177,9 +3181,67 @@
 };
 
 TEST_F(OutputPostFramebufferTest, ifNotEnabledDoesNothing) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
     mOutput.mState.isEnabled = false;
+    EXPECT_CALL(mOutput, executeCommands()).Times(0);
+    EXPECT_CALL(mOutput, presentFrame()).Times(0);
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, ifNotEnabledExecutesCommandsIfFlush) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
+    mOutput.mState.isEnabled = false;
+    EXPECT_CALL(mOutput, executeCommands());
+    EXPECT_CALL(mOutput, presentFrame()).Times(0);
+
+    constexpr bool kFlushEvenWhenDisabled = true;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, ifEnabledDoNotExecuteCommands) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
+    mOutput.mState.isEnabled = true;
+
+    compositionengine::Output::FrameFences frameFences;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // This should only be called for disabled outputs. This test's goal is to verify this line;
+    // the other expectations help satisfy the StrictMocks.
+    EXPECT_CALL(mOutput, executeCommands()).Times(0);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    constexpr bool kFlushEvenWhenDisabled = true;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, ifEnabledDoNotExecuteCommands2) {
+    // Same test as ifEnabledDoNotExecuteCommands, but with this variable set to false.
+    constexpr bool kFlushEvenWhenDisabled = false;
+
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
+    mOutput.mState.isEnabled = true;
+
+    compositionengine::Output::FrameFences frameFences;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // This should only be called for disabled outputs. This test's goal is to verify this line;
+    // the other expectations help satisfy the StrictMocks.
+    EXPECT_CALL(mOutput, executeCommands()).Times(0);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCompleted) {
@@ -3197,7 +3259,8 @@
     EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = true;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
@@ -3241,7 +3304,8 @@
                 EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get());
             });
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) {
@@ -3282,7 +3346,8 @@
                 EXPECT_EQ(FenceResult(layer3Fence), releaseFence);
             });
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
@@ -3308,7 +3373,8 @@
     EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed).WillOnce(Return());
     EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed).WillOnce(Return());
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) {
@@ -3333,7 +3399,8 @@
     EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return());
     EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return());
     EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return());
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
@@ -3381,7 +3448,8 @@
                 EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
             });
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 
     // After the call the list of released layers should have been cleared.
     EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
@@ -3429,7 +3497,8 @@
                 EXPECT_EQ(FenceResult(presentFence), fenceResult);
             });
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 
     // After the call the list of released layers should have been cleared.
     EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
@@ -5272,8 +5341,9 @@
     struct OutputPartialMock : public OutputPrepareFrameAsyncTest::OutputPartialMock {
         // Set up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
-        MOCK_METHOD0(presentFrameAndReleaseLayersAsync, ftl::Future<std::monostate>());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled));
+        MOCK_METHOD(ftl::Future<std::monostate>, presentFrameAndReleaseLayersAsync,
+                    (bool flushEvenWhenDisabled));
     };
     OutputPresentFrameAndReleaseLayersAsyncTest() {
         mOutput->setDisplayColorProfileForTest(
@@ -5290,16 +5360,16 @@
 };
 
 TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, notCalledWhenNotRequested) {
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()).Times(0);
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(_)).Times(0);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(_)).Times(1);
 
     mOutput->present(mRefreshArgs);
 }
 
 TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledWhenRequested) {
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(false))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(0);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(_)).Times(0);
 
     mOutput->offloadPresentNextFrame();
     mOutput->present(mRefreshArgs);
@@ -5307,9 +5377,10 @@
 
 TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledForOneFrame) {
     ::testing::InSequence inseq;
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(kFlushEvenWhenDisabled))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)).Times(1);
 
     mOutput->offloadPresentNextFrame();
     mOutput->present(mRefreshArgs);
@@ -5390,5 +5461,59 @@
     mOutput.updateProtectedContentState();
 }
 
+struct OutputPresentFrameAndReleaseLayersTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test (and functions we can
+        // ignore) to use mock implementations.
+        MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(updateCompositionState,
+                     void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(planComposition, void());
+        MOCK_METHOD1(writeCompositionState, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(beginFrame, void());
+        MOCK_METHOD0(prepareFrame, void());
+        MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult());
+        MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&));
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override));
+        MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
+    };
+
+    OutputPresentFrameAndReleaseLayersTest() {
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
+    }
+
+    NiceMock<OutputPartialMock> mOutput;
+};
+
+TEST_F(OutputPresentFrameAndReleaseLayersTest, noBuffersToUncache) {
+    CompositionRefreshArgs args;
+    ASSERT_TRUE(args.bufferIdsToUncache.empty());
+    mOutput.editState().isEnabled = false;
+
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
+
+    mOutput.present(args);
+}
+
+TEST_F(OutputPresentFrameAndReleaseLayersTest, buffersToUncache) {
+    CompositionRefreshArgs args;
+    args.bufferIdsToUncache.push_back(1);
+    mOutput.editState().isEnabled = false;
+
+    constexpr bool kFlushEvenWhenDisabled = true;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
+
+    mOutput.present(args);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
index f093384..a6a9bec 100644
--- a/services/surfaceflinger/Display/DisplayModeController.cpp
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -20,30 +20,221 @@
 
 #include "Display/DisplayModeController.h"
 #include "Display/DisplaySnapshot.h"
+#include "DisplayHardware/HWComposer.h"
 
+#include <common/FlagManager.h>
+#include <ftl/concat.h>
+#include <ftl/expected.h>
 #include <log/log.h>
 
 namespace android::display {
 
+template <size_t N>
+inline std::string DisplayModeController::Display::concatId(const char (&str)[N]) const {
+    return std::string(ftl::Concat(str, ' ', snapshot.get().displayId().value).str());
+}
+
+DisplayModeController::Display::Display(DisplaySnapshotRef snapshot,
+                                        RefreshRateSelectorPtr selectorPtr)
+      : snapshot(snapshot),
+        selectorPtr(std::move(selectorPtr)),
+        pendingModeFpsTrace(concatId("PendingModeFps")),
+        activeModeFpsTrace(concatId("ActiveModeFps")),
+        renderRateFpsTrace(concatId("RenderRateFps")),
+        hasDesiredModeTrace(concatId("HasDesiredMode"), false) {}
+
+void DisplayModeController::registerDisplay(PhysicalDisplayId displayId,
+                                            DisplaySnapshotRef snapshotRef,
+                                            RefreshRateSelectorPtr selectorPtr) {
+    std::lock_guard lock(mDisplayLock);
+    mDisplays.emplace_or_replace(displayId, std::make_unique<Display>(snapshotRef, selectorPtr));
+}
+
 void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef,
                                             DisplayModeId activeModeId,
                                             scheduler::RefreshRateSelector::Config config) {
     const auto& snapshot = snapshotRef.get();
     const auto displayId = snapshot.displayId();
 
-    mDisplays.emplace_or_replace(displayId, snapshotRef, snapshot.displayModes(), activeModeId,
-                                 config);
+    std::lock_guard lock(mDisplayLock);
+    mDisplays.emplace_or_replace(displayId,
+                                 std::make_unique<Display>(snapshotRef, snapshot.displayModes(),
+                                                           activeModeId, config));
 }
 
 void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) {
+    std::lock_guard lock(mDisplayLock);
     const bool ok = mDisplays.erase(displayId);
     ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str());
 }
 
-auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) -> RefreshRateSelectorPtr {
+auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) const
+        -> RefreshRateSelectorPtr {
+    std::lock_guard lock(mDisplayLock);
     return mDisplays.get(displayId)
-            .transform([](const Display& display) { return display.selectorPtr; })
+            .transform([](const DisplayPtr& displayPtr) { return displayPtr->selectorPtr; })
             .value_or(nullptr);
 }
 
+auto DisplayModeController::setDesiredMode(PhysicalDisplayId displayId,
+                                           DisplayModeRequest&& desiredMode) -> DesiredModeAction {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get();
+
+    {
+        ATRACE_NAME(displayPtr->concatId(__func__).c_str());
+        ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str());
+
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+
+        if (auto& desiredModeOpt = displayPtr->desiredModeOpt) {
+            // A mode transition was already scheduled, so just override the desired mode.
+            const bool emitEvent = desiredModeOpt->emitEvent;
+            const bool force = desiredModeOpt->force;
+            desiredModeOpt = std::move(desiredMode);
+            desiredModeOpt->emitEvent |= emitEvent;
+            if (FlagManager::getInstance().connected_display()) {
+                desiredModeOpt->force |= force;
+            }
+            return DesiredModeAction::None;
+        }
+
+        // If the desired mode is already active...
+        const auto activeMode = displayPtr->selectorPtr->getActiveMode();
+        if (const auto& desiredModePtr = desiredMode.mode.modePtr;
+            !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
+            if (activeMode == desiredMode.mode) {
+                return DesiredModeAction::None;
+            }
+
+            // ...but the render rate changed:
+            setActiveModeLocked(displayId, desiredModePtr->getId(), desiredModePtr->getVsyncRate(),
+                                desiredMode.mode.fps);
+            return DesiredModeAction::InitiateRenderRateSwitch;
+        }
+
+        // Restore peak render rate to schedule the next frame as soon as possible.
+        setActiveModeLocked(displayId, activeMode.modePtr->getId(),
+                            activeMode.modePtr->getVsyncRate(), activeMode.modePtr->getPeakFps());
+
+        // Initiate a mode change.
+        displayPtr->desiredModeOpt = std::move(desiredMode);
+        displayPtr->hasDesiredModeTrace = true;
+    }
+
+    return DesiredModeAction::InitiateDisplayModeSwitch;
+}
+
+auto DisplayModeController::getDesiredMode(PhysicalDisplayId displayId) const
+        -> DisplayModeRequestOpt {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        return displayPtr->desiredModeOpt;
+    }
+}
+
+auto DisplayModeController::getPendingMode(PhysicalDisplayId displayId) const
+        -> DisplayModeRequestOpt {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        return displayPtr->pendingModeOpt;
+    }
+}
+
+bool DisplayModeController::isModeSetPending(PhysicalDisplayId displayId) const {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        return displayPtr->isModeSetPending;
+    }
+}
+
+scheduler::FrameRateMode DisplayModeController::getActiveMode(PhysicalDisplayId displayId) const {
+    return selectorPtrFor(displayId)->getActiveMode();
+}
+
+void DisplayModeController::clearDesiredMode(PhysicalDisplayId displayId) {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        displayPtr->desiredModeOpt.reset();
+        displayPtr->hasDesiredModeTrace = false;
+    }
+}
+
+bool DisplayModeController::initiateModeChange(PhysicalDisplayId displayId,
+                                               DisplayModeRequest&& desiredMode,
+                                               const hal::VsyncPeriodChangeConstraints& constraints,
+                                               hal::VsyncPeriodChangeTimeline& outTimeline) {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();
+
+    // TODO: b/255635711 - Flow the DisplayModeRequest through the desired/pending/active states.
+    // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not
+    // cleared until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been
+    // consumed at this point, so clear the `force` flag to prevent an endless loop of
+    // `initiateModeChange`.
+    if (FlagManager::getInstance().connected_display()) {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        if (displayPtr->desiredModeOpt) {
+            displayPtr->desiredModeOpt->force = false;
+        }
+    }
+
+    displayPtr->pendingModeOpt = std::move(desiredMode);
+    displayPtr->isModeSetPending = true;
+
+    const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr;
+
+    if (mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(), constraints,
+                                                   &outTimeline) != OK) {
+        return false;
+    }
+
+    ATRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+    return true;
+}
+
+void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                               Fps vsyncRate, Fps renderFps) {
+    std::lock_guard lock(mDisplayLock);
+    setActiveModeLocked(displayId, modeId, vsyncRate, renderFps);
+
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+    displayPtr->isModeSetPending = false;
+}
+
+void DisplayModeController::setActiveMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                          Fps vsyncRate, Fps renderFps) {
+    std::lock_guard lock(mDisplayLock);
+    setActiveModeLocked(displayId, modeId, vsyncRate, renderFps);
+}
+
+void DisplayModeController::setActiveModeLocked(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                                Fps vsyncRate, Fps renderFps) {
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+
+    ATRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
+    ATRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());
+
+    displayPtr->selectorPtr->setActiveMode(modeId, renderFps);
+
+    if (mActiveModeListener) {
+        mActiveModeListener(displayId, vsyncRate, renderFps);
+    }
+}
+
 } // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h
index b6a6bee..258b04b 100644
--- a/services/surfaceflinger/Display/DisplayModeController.h
+++ b/services/surfaceflinger/Display/DisplayModeController.h
@@ -17,16 +17,26 @@
 #pragma once
 
 #include <memory>
+#include <mutex>
+#include <string>
 #include <utility>
 
 #include <android-base/thread_annotations.h>
+#include <ftl/function.h>
+#include <ftl/optional.h>
 #include <ui/DisplayId.h>
 #include <ui/DisplayMap.h>
 
+#include "Display/DisplayModeRequest.h"
 #include "Display/DisplaySnapshotRef.h"
 #include "DisplayHardware/DisplayMode.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "ThreadContext.h"
+#include "TracedOrdinal.h"
+
+namespace android {
+class HWComposer;
+} // namespace android
 
 namespace android::display {
 
@@ -34,40 +44,97 @@
 // certain heuristic signals.
 class DisplayModeController {
 public:
-    // The referenced DisplaySnapshot must outlive the registration.
-    void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config)
-            REQUIRES(kMainThreadContext);
-    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext);
+    using ActiveModeListener = ftl::Function<void(PhysicalDisplayId, Fps vsyncRate, Fps renderFps)>;
 
-    // TODO(b/241285876): Remove once ownership is no longer shared with DisplayDevice.
+    DisplayModeController() = default;
+
+    void setHwComposer(HWComposer* composerPtr) { mComposerPtr = composerPtr; }
+    void setActiveModeListener(const ActiveModeListener& listener) {
+        mActiveModeListener = listener;
+    }
+
+    // TODO: b/241285876 - Remove once ownership is no longer shared with DisplayDevice.
     using RefreshRateSelectorPtr = std::shared_ptr<scheduler::RefreshRateSelector>;
 
-    // Returns `nullptr` if the display is no longer registered (or never was).
-    RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) REQUIRES(kMainThreadContext);
-
     // Used by tests to inject an existing RefreshRateSelector.
-    // TODO(b/241285876): Remove this.
-    void registerDisplay(PhysicalDisplayId displayId, DisplaySnapshotRef snapshotRef,
-                         RefreshRateSelectorPtr selectorPtr) {
-        mDisplays.emplace_or_replace(displayId, snapshotRef, selectorPtr);
-    }
+    // TODO: b/241285876 - Remove this.
+    void registerDisplay(PhysicalDisplayId, DisplaySnapshotRef, RefreshRateSelectorPtr)
+            EXCLUDES(mDisplayLock);
+
+    // The referenced DisplaySnapshot must outlive the registration.
+    void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+    // Returns `nullptr` if the display is no longer registered (or never was).
+    RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
+
+    enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
+
+    DesiredModeAction setDesiredMode(PhysicalDisplayId, DisplayModeRequest&&)
+            EXCLUDES(mDisplayLock);
+
+    using DisplayModeRequestOpt = ftl::Optional<DisplayModeRequest>;
+
+    DisplayModeRequestOpt getDesiredMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
+    void clearDesiredMode(PhysicalDisplayId) EXCLUDES(mDisplayLock);
+
+    DisplayModeRequestOpt getPendingMode(PhysicalDisplayId) const REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+    bool isModeSetPending(PhysicalDisplayId) const REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+
+    scheduler::FrameRateMode getActiveMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
+
+    bool initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&,
+                            const hal::VsyncPeriodChangeConstraints&,
+                            hal::VsyncPeriodChangeTimeline& outTimeline)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+    void finalizeModeChange(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+    void setActiveMode(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
+            EXCLUDES(mDisplayLock);
 
 private:
     struct Display {
-        Display(DisplaySnapshotRef snapshot, RefreshRateSelectorPtr selectorPtr)
-              : snapshot(snapshot), selectorPtr(std::move(selectorPtr)) {}
+        template <size_t N>
+        std::string concatId(const char (&)[N]) const;
 
+        Display(DisplaySnapshotRef, RefreshRateSelectorPtr);
         Display(DisplaySnapshotRef snapshot, DisplayModes modes, DisplayModeId activeModeId,
                 scheduler::RefreshRateSelector::Config config)
               : Display(snapshot,
                         std::make_shared<scheduler::RefreshRateSelector>(std::move(modes),
                                                                          activeModeId, config)) {}
-
         const DisplaySnapshotRef snapshot;
         const RefreshRateSelectorPtr selectorPtr;
+
+        const std::string pendingModeFpsTrace;
+        const std::string activeModeFpsTrace;
+        const std::string renderRateFpsTrace;
+
+        std::mutex desiredModeLock;
+        DisplayModeRequestOpt desiredModeOpt GUARDED_BY(desiredModeLock);
+        TracedOrdinal<bool> hasDesiredModeTrace GUARDED_BY(desiredModeLock);
+
+        DisplayModeRequestOpt pendingModeOpt GUARDED_BY(kMainThreadContext);
+        bool isModeSetPending GUARDED_BY(kMainThreadContext) = false;
     };
 
-    ui::PhysicalDisplayMap<PhysicalDisplayId, Display> mDisplays;
+    using DisplayPtr = std::unique_ptr<Display>;
+
+    void setActiveModeLocked(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
+            REQUIRES(mDisplayLock);
+
+    // Set once when initializing the DisplayModeController, which the HWComposer must outlive.
+    HWComposer* mComposerPtr = nullptr;
+
+    ActiveModeListener mActiveModeListener;
+
+    mutable std::mutex mDisplayLock;
+    ui::PhysicalDisplayMap<PhysicalDisplayId, DisplayPtr> mDisplays GUARDED_BY(mDisplayLock);
 };
 
 } // namespace android::display
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 38cf053..27ea4a9 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,7 +24,6 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -36,6 +35,7 @@
 #include <compositionengine/RenderSurfaceCreationArgs.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <configstore/Utils.h>
+#include <ftl/concat.h>
 #include <log/log.h>
 #include <system/window.h>
 
@@ -64,15 +64,11 @@
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
         mCompositionDisplay{args.compositionDisplay},
-        mPendingModeFpsTrace(concatId("PendingModeFps")),
-        mActiveModeFpsTrace(concatId("ActiveModeFps")),
-        mRenderRateFpsTrace(concatId("RenderRateFps")),
         mPhysicalOrientation(args.physicalOrientation),
         mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
-        mRefreshRateSelector(std::move(args.refreshRateSelector)),
-        mHasDesiredModeTrace(concatId("HasDesiredMode"), false) {
+        mRefreshRateSelector(std::move(args.refreshRateSelector)) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
     mCompositionDisplay->editState().isProtected = args.isProtected;
     mCompositionDisplay->createRenderSurface(
@@ -204,47 +200,6 @@
     return mPowerMode != hal::PowerMode::OFF;
 }
 
-void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
-    ATRACE_INT(mActiveModeFpsTrace.c_str(), vsyncRate.getIntValue());
-    ATRACE_INT(mRenderRateFpsTrace.c_str(), renderFps.getIntValue());
-
-    mRefreshRateSelector->setActiveMode(modeId, renderFps);
-    updateRefreshRateOverlayRate(vsyncRate, renderFps);
-}
-
-bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode,
-                                       const hal::VsyncPeriodChangeConstraints& constraints,
-                                       hal::VsyncPeriodChangeTimeline& outTimeline) {
-    // TODO(b/255635711): Flow the DisplayModeRequest through the desired/pending/active states. For
-    // now, `desiredMode` and `mDesiredModeOpt` are one and the same, but the latter is not cleared
-    // until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been consumed
-    // at this point, so clear the `force` flag to prevent an endless loop of `initiateModeChange`.
-    if (FlagManager::getInstance().connected_display()) {
-        std::scoped_lock lock(mDesiredModeLock);
-        if (mDesiredModeOpt) {
-            mDesiredModeOpt->force = false;
-        }
-    }
-
-    mPendingModeOpt = std::move(desiredMode);
-    mIsModeSetPending = true;
-
-    const auto& mode = *mPendingModeOpt->mode.modePtr;
-
-    if (mHwComposer.setActiveModeWithConstraints(getPhysicalId(), mode.getHwcId(), constraints,
-                                                 &outTimeline) != OK) {
-        return false;
-    }
-
-    ATRACE_INT(mPendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
-    return true;
-}
-
-void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
-    setActiveMode(modeId, vsyncRate, renderFps);
-    mIsModeSetPending = false;
-}
-
 nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const {
     const auto physicalId = getPhysicalId();
     if (!mHwComposer.isConnected(physicalId)) {
@@ -450,8 +405,9 @@
     }
 }
 
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner,
-                                             bool showRenderRate, bool showInMiddle) {
+void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, Fps refreshRate,
+                                             Fps renderFps, bool showSpinner, bool showRenderRate,
+                                             bool showInMiddle) {
     if (!enable) {
         mRefreshRateOverlay.reset();
         return;
@@ -474,14 +430,12 @@
         features |= RefreshRateOverlay::Features::SetByHwc;
     }
 
-    // TODO(b/296636258) Update to use the render rate range in VRR mode.
     const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
     mRefreshRateOverlay = RefreshRateOverlay::create(fpsRange, features);
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setLayerStack(getLayerStack());
         mRefreshRateOverlay->setViewport(getSize());
-        updateRefreshRateOverlayRate(getActiveMode().modePtr->getVsyncRate(), getActiveMode().fps,
-                                     setByHwc);
+        updateRefreshRateOverlayRate(refreshRate, renderFps, setByHwc);
     }
 }
 
@@ -489,6 +443,9 @@
     ATRACE_CALL();
     if (mRefreshRateOverlay) {
         if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
+            if (mRefreshRateSelector->isVrrDevice() && !mRefreshRateOverlay->isSetByHwc()) {
+                refreshRate = renderFps;
+            }
             mRefreshRateOverlay->changeRefreshRate(refreshRate, renderFps);
         } else {
             mRefreshRateOverlay->changeRenderRate(renderFps);
@@ -529,57 +486,6 @@
     }
 }
 
-auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction {
-    ATRACE_NAME(concatId(__func__).c_str());
-    ALOGD("%s %s", concatId(__func__).c_str(), to_string(desiredMode).c_str());
-
-    std::scoped_lock lock(mDesiredModeLock);
-    if (mDesiredModeOpt) {
-        // A mode transition was already scheduled, so just override the desired mode.
-        const bool emitEvent = mDesiredModeOpt->emitEvent;
-        const bool force = mDesiredModeOpt->force;
-        mDesiredModeOpt = std::move(desiredMode);
-        mDesiredModeOpt->emitEvent |= emitEvent;
-        if (FlagManager::getInstance().connected_display()) {
-            mDesiredModeOpt->force |= force;
-        }
-        return DesiredModeAction::None;
-    }
-
-    // If the desired mode is already active...
-    const auto activeMode = refreshRateSelector().getActiveMode();
-    if (const auto& desiredModePtr = desiredMode.mode.modePtr;
-        !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
-        if (activeMode == desiredMode.mode) {
-            return DesiredModeAction::None;
-        }
-
-        // ...but the render rate changed:
-        setActiveMode(desiredModePtr->getId(), desiredModePtr->getVsyncRate(),
-                      desiredMode.mode.fps);
-        return DesiredModeAction::InitiateRenderRateSwitch;
-    }
-
-    setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
-                  activeMode.modePtr->getPeakFps());
-
-    // Initiate a mode change.
-    mDesiredModeOpt = std::move(desiredMode);
-    mHasDesiredModeTrace = true;
-    return DesiredModeAction::InitiateDisplayModeSwitch;
-}
-
-auto DisplayDevice::getDesiredMode() const -> DisplayModeRequestOpt {
-    std::scoped_lock lock(mDesiredModeLock);
-    return mDesiredModeOpt;
-}
-
-void DisplayDevice::clearDesiredMode() {
-    std::scoped_lock lock(mDesiredModeLock);
-    mDesiredModeOpt.reset();
-    mHasDesiredModeTrace = false;
-}
-
 void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) {
     using fps_approx_ops::operator<=;
     if (mRequestedRefreshRate <= 0_Hz) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index a21559f..3cc8cf5 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -23,8 +23,6 @@
 #include <android-base/thread_annotations.h>
 #include <android/native_window.h>
 #include <binder/IBinder.h>
-#include <ftl/concat.h>
-#include <ftl/optional.h>
 #include <gui/LayerState.h>
 #include <math/mat4.h>
 #include <renderengine/RenderEngine.h>
@@ -42,7 +40,6 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include "Display/DisplayModeRequest.h"
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
@@ -183,37 +180,6 @@
 
     ui::Dataspace getCompositionDataSpace() const;
 
-    /* ------------------------------------------------------------------------
-     * Display mode management.
-     */
-
-    enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
-
-    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock);
-
-    using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
-
-    DisplayModeRequestOpt getDesiredMode() const EXCLUDES(mDesiredModeLock);
-    void clearDesiredMode() EXCLUDES(mDesiredModeLock);
-
-    DisplayModeRequestOpt getPendingMode() const REQUIRES(kMainThreadContext) {
-        return mPendingModeOpt;
-    }
-    bool isModeSetPending() const REQUIRES(kMainThreadContext) { return mIsModeSetPending; }
-
-    scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) {
-        return mRefreshRateSelector->getActiveMode();
-    }
-
-    void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
-
-    bool initiateModeChange(display::DisplayModeRequest&&, const hal::VsyncPeriodChangeConstraints&,
-                            hal::VsyncPeriodChangeTimeline& outTimeline)
-            REQUIRES(kMainThreadContext);
-
-    void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
-            REQUIRES(kMainThreadContext);
-
     scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; }
 
     // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice.
@@ -221,13 +187,14 @@
         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);
+    // TODO(b/241285876): Move overlay to DisplayModeController.
+    void enableRefreshRateOverlay(bool enable, bool setByHwc, Fps refreshRate, Fps renderFps,
+                                  bool showSpinner, bool showRenderRate, bool showInMiddle)
+            REQUIRES(kMainThreadContext);
     void updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
+    void animateOverlay();
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
 
     // Enables an overlay to be display with the hdr/sdr ratio
@@ -249,11 +216,6 @@
     void dump(utils::Dumper&) const;
 
 private:
-    template <size_t N>
-    inline std::string concatId(const char (&str)[N]) const {
-        return std::string(ftl::Concat(str, ' ', getId().value).str());
-    }
-
     const sp<SurfaceFlinger> mFlinger;
     HWComposer& mHwComposer;
     const wp<IBinder> mDisplayToken;
@@ -262,9 +224,6 @@
     const std::shared_ptr<compositionengine::Display> mCompositionDisplay;
 
     std::string mDisplayName;
-    std::string mPendingModeFpsTrace;
-    std::string mActiveModeFpsTrace;
-    std::string mRenderRateFpsTrace;
 
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
@@ -296,13 +255,6 @@
     std::unique_ptr<HdrSdrRatioOverlay> mHdrSdrRatioOverlay;
     // This parameter is only used for hdr/sdr ratio overlay
     float mHdrSdrRatio = 1.0f;
-
-    mutable std::mutex mDesiredModeLock;
-    DisplayModeRequestOpt mDesiredModeOpt GUARDED_BY(mDesiredModeLock);
-    TracedOrdinal<bool> mHasDesiredModeTrace GUARDED_BY(mDesiredModeLock);
-
-    DisplayModeRequestOpt mPendingModeOpt GUARDED_BY(kMainThreadContext);
-    bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false;
 };
 
 struct DisplayDeviceState {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 84f668d..8c0f81e 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -79,6 +79,9 @@
                  DisplayType type)
       : mComposer(composer), mCapabilities(capabilities), mId(id), mType(type) {
     ALOGV("Created display %" PRIu64, id);
+    if (mType == hal::DisplayType::VIRTUAL) {
+        loadDisplayCapabilities();
+    }
 }
 
 Display::~Display() {
@@ -499,29 +502,7 @@
     auto intError = mComposer.setPowerMode(mId, intMode);
 
     if (mode == PowerMode::ON) {
-        std::call_once(mDisplayCapabilityQueryFlag, [this]() {
-            std::vector<DisplayCapability> tmpCapabilities;
-            auto error =
-                    static_cast<Error>(mComposer.getDisplayCapabilities(mId, &tmpCapabilities));
-            if (error == Error::NONE) {
-                std::scoped_lock lock(mDisplayCapabilitiesMutex);
-                mDisplayCapabilities.emplace();
-                for (auto capability : tmpCapabilities) {
-                    mDisplayCapabilities->emplace(capability);
-                }
-            } else if (error == Error::UNSUPPORTED) {
-                std::scoped_lock lock(mDisplayCapabilitiesMutex);
-                mDisplayCapabilities.emplace();
-                if (mCapabilities.count(AidlCapability::SKIP_CLIENT_COLOR_TRANSFORM)) {
-                    mDisplayCapabilities->emplace(DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM);
-                }
-                bool dozeSupport = false;
-                error = static_cast<Error>(mComposer.getDozeSupport(mId, &dozeSupport));
-                if (error == Error::NONE && dozeSupport) {
-                    mDisplayCapabilities->emplace(DisplayCapability::DOZE);
-                }
-            }
-        });
+        loadDisplayCapabilities();
     }
 
     return static_cast<Error>(intError);
@@ -653,6 +634,32 @@
     auto it = mLayers.find(id);
     return it != mLayers.end() ? it->second.lock() : nullptr;
 }
+
+void Display::loadDisplayCapabilities() {
+    std::call_once(mDisplayCapabilityQueryFlag, [this]() {
+        std::vector<DisplayCapability> tmpCapabilities;
+        auto error =
+                static_cast<Error>(mComposer.getDisplayCapabilities(mId, &tmpCapabilities));
+        if (error == Error::NONE) {
+            std::scoped_lock lock(mDisplayCapabilitiesMutex);
+            mDisplayCapabilities.emplace();
+            for (auto capability : tmpCapabilities) {
+                mDisplayCapabilities->emplace(capability);
+            }
+        } else if (error == Error::UNSUPPORTED) {
+            std::scoped_lock lock(mDisplayCapabilitiesMutex);
+            mDisplayCapabilities.emplace();
+            if (mCapabilities.count(AidlCapability::SKIP_CLIENT_COLOR_TRANSFORM)) {
+                mDisplayCapabilities->emplace(DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM);
+            }
+            bool dozeSupport = false;
+            error = static_cast<Error>(mComposer.getDozeSupport(mId, &dozeSupport));
+            if (error == Error::NONE && dozeSupport) {
+                mDisplayCapabilities->emplace(DisplayCapability::DOZE);
+            }
+        }
+    });
+}
 } // namespace impl
 
 // Layer methods
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index de044e0..5b94831 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -278,6 +278,7 @@
     hal::Error getPhysicalDisplayOrientation(Hwc2::AidlTransform* outTransform) const override;
 
 private:
+    void loadDisplayCapabilities();
 
     // This may fail (and return a null pointer) if no layer with this ID exists
     // on this display
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 3cfb9ca..3d285a8 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -600,6 +600,13 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::executeCommands(HalDisplayId displayId) {
+    auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
+    auto error = static_cast<hal::Error>(mComposer->executeCommands(hwcDisplay->getId()));
+    RETURN_IF_HWC_ERROR_FOR("executeCommands", error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
 status_t HWComposer::setPowerMode(PhysicalDisplayId displayId, hal::PowerMode mode) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index bc32cda..9368b7b 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -160,6 +160,8 @@
             HalDisplayId,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) = 0;
 
+    virtual status_t executeCommands(HalDisplayId) = 0;
+
     // set power mode
     virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0;
 
@@ -361,6 +363,8 @@
             HalDisplayId,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) override;
 
+    status_t executeCommands(HalDisplayId) override;
+
     // set power mode
     status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override;
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 2b20de3..39a6b77 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -260,27 +260,36 @@
     hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Attached);
 }
 
-void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) {
-    if (root->mLayer) {
-        attachToRelativeParent(root);
-    }
-    for (auto& [child, childVariant] : root->mChildren) {
-        if (childVariant == LayerHierarchy::Variant::Detached ||
-            childVariant == LayerHierarchy::Variant::Attached) {
-            attachHierarchyToRelativeParent(child);
+std::vector<LayerHierarchy*> LayerHierarchyBuilder::getDescendants(LayerHierarchy* root) {
+    std::vector<LayerHierarchy*> hierarchies;
+    hierarchies.push_back(root);
+    std::vector<LayerHierarchy*> descendants;
+    for (size_t i = 0; i < hierarchies.size(); i++) {
+        LayerHierarchy* hierarchy = hierarchies[i];
+        if (hierarchy->mLayer) {
+            descendants.push_back(hierarchy);
         }
+        for (auto& [child, childVariant] : hierarchy->mChildren) {
+            if (childVariant == LayerHierarchy::Variant::Detached ||
+                childVariant == LayerHierarchy::Variant::Attached) {
+                hierarchies.push_back(child);
+            }
+        }
+    }
+    return descendants;
+}
+
+void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) {
+    std::vector<LayerHierarchy*> hierarchiesToAttach = getDescendants(root);
+    for (LayerHierarchy* hierarchy : hierarchiesToAttach) {
+        attachToRelativeParent(hierarchy);
     }
 }
 
 void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) {
-    if (root->mLayer) {
-        detachFromRelativeParent(root);
-    }
-    for (auto& [child, childVariant] : root->mChildren) {
-        if (childVariant == LayerHierarchy::Variant::Detached ||
-            childVariant == LayerHierarchy::Variant::Attached) {
-            detachHierarchyFromRelativeParent(child);
-        }
+    std::vector<LayerHierarchy*> hierarchiesToDetach = getDescendants(root);
+    for (LayerHierarchy* hierarchy : hierarchiesToDetach) {
+        detachFromRelativeParent(hierarchy);
     }
 }
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index 69710be..d023f9e 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -218,6 +218,7 @@
     void detachFromParent(LayerHierarchy*);
     void attachToRelativeParent(LayerHierarchy*);
     void detachFromRelativeParent(LayerHierarchy*);
+    std::vector<LayerHierarchy*> getDescendants(LayerHierarchy*);
     void attachHierarchyToRelativeParent(LayerHierarchy*);
     void detachHierarchyFromRelativeParent(LayerHierarchy*);
     void init(const std::vector<std::unique_ptr<RequestedLayerState>>&);
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 4b0618e..dd5e8bd 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -152,6 +152,10 @@
             if (swapErase(linkedLayer->mirrorIds, layer.id)) {
                 linkedLayer->changes |= RequestedLayerState::Changes::Mirror;
             }
+            if (linkedLayer->layerIdToMirror == layer.id) {
+                linkedLayer->layerIdToMirror = UNASSIGNED_LAYER_ID;
+                linkedLayer->changes |= RequestedLayerState::Changes::Mirror;
+            }
             if (linkedLayer->touchCropId == layer.id) {
                 linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
             }
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 6d4b0b5..a4ffd51 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -256,6 +256,9 @@
 }
 
 void updateVisibility(LayerSnapshot& snapshot, bool visible) {
+    if (snapshot.isVisible != visible) {
+        snapshot.changes |= RequestedLayerState::Changes::Visibility;
+    }
     snapshot.isVisible = visible;
 
     // TODO(b/238781169) we are ignoring this compat for now, since we will have
@@ -314,6 +317,21 @@
     }
 }
 
+void updateMetadataAndGameMode(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+                               const LayerSnapshotBuilder::Args& args,
+                               const LayerSnapshot& parentSnapshot) {
+    if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
+        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
+                ? requested.gameMode
+                : parentSnapshot.gameMode;
+    }
+    updateMetadata(snapshot, requested, args);
+    if (args.includeMetadata) {
+        snapshot.layerMetadata = parentSnapshot.layerMetadata;
+        snapshot.layerMetadata.merge(requested.metadata);
+    }
+}
+
 void clearChanges(LayerSnapshot& snapshot) {
     snapshot.changes.clear();
     snapshot.clientChanges = 0;
@@ -759,6 +777,11 @@
                                  RequestedLayerState::Changes::Input)) {
             updateInput(snapshot, requested, parentSnapshot, path, args);
         }
+        if (forceUpdate ||
+            (args.includeMetadata &&
+             snapshot.changes.test(RequestedLayerState::Changes::Metadata))) {
+            updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
+        }
         return;
     }
 
@@ -798,15 +821,8 @@
         }
     }
 
-    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
-        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
-                ? requested.gameMode
-                : parentSnapshot.gameMode;
-        updateMetadata(snapshot, requested, args);
-        if (args.includeMetadata) {
-            snapshot.layerMetadata = parentSnapshot.layerMetadata;
-            snapshot.layerMetadata.merge(requested.metadata);
-        }
+    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) {
+        updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged ||
@@ -1175,6 +1191,15 @@
     }
 }
 
+void LayerSnapshotBuilder::forEachSnapshot(const Visitor& visitor,
+                                           const ConstPredicate& predicate) {
+    for (int i = 0; i < mNumInterestingSnapshots; i++) {
+        std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
+        if (!predicate(*snapshot)) continue;
+        visitor(snapshot);
+    }
+}
+
 void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const {
     for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
         LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 1cec018..dbbad76 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -86,6 +86,11 @@
     // Visit each visible snapshot in z-order and move the snapshot if needed
     void forEachVisibleSnapshot(const Visitor& visitor);
 
+    typedef std::function<bool(const LayerSnapshot& snapshot)> ConstPredicate;
+    // Visit each snapshot that satisfies the predicate and move the snapshot if needed with visible
+    // snapshots in z-order
+    void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate);
+
     // Visit each snapshot interesting to input reverse z-order
     void forEachInputSnapshot(const ConstVisitor& visitor) const;
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 3e8d740..c3c2999 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -328,6 +328,7 @@
                 changes |= RequestedLayerState::Changes::GameMode;
             }
         }
+        changes |= RequestedLayerState::Changes::Metadata;
     }
     if (clientState.what & layer_state_t::eFrameRateChanged) {
         const auto compatibility =
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 6b97e2f..d27bfd2 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3149,8 +3149,7 @@
 
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
-                      bool isAutoTimestamp, std::optional<nsecs_t> dequeueTime,
-                      const FrameTimelineInfo& info) {
+                      bool isAutoTimestamp, const FrameTimelineInfo& info) {
     ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
 
     const bool frameNumberChanged =
@@ -3224,10 +3223,11 @@
 
     setFrameTimelineVsyncForBufferTransaction(info, postTime);
 
-    if (dequeueTime && *dequeueTime != 0) {
+    if (bufferData.dequeueTime > 0) {
         const uint64_t bufferId = mDrawingState.buffer->getId();
         mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str());
-        mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime,
+        mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber,
+                                               bufferData.dequeueTime,
                                                FrameTracer::FrameEvent::DEQUEUE);
         mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime,
                                                FrameTracer::FrameEvent::QUEUE);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 9db7664..b9fcd5c 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -312,7 +312,7 @@
     bool setBuffer(std::shared_ptr<renderengine::ExternalTexture>& /* buffer */,
                    const BufferData& /* bufferData */, nsecs_t /* postTime */,
                    nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                   std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
+                   const FrameTimelineInfo& /*info*/);
     void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index b40f332..9527a99 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -200,19 +200,13 @@
     BufferCache::const_iterator it =
             mBufferCache.find({refreshRate.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 refreshRate may be outside of this range if the display
-        // has changed its set of supported refresh rates.
-        const int displayIntFps = std::clamp(refreshRate.getIntValue(), minFps, maxFps);
+        // Clamp to supported refresh rate range: the current refresh rate may be outside of this
+        // range if the display has changed its set of supported refresh rates.
+        const int refreshIntFps = std::clamp(refreshRate.getIntValue(), 0, maxFps);
         const int renderIntFps = renderFps.getIntValue();
-
-        // Ensure non-zero range to avoid division by zero.
-        const float fpsScale =
-                static_cast<float>(displayIntFps - minFps) / std::max(1, maxFps - minFps);
+        const float fpsScale = static_cast<float>(refreshIntFps) / maxFps;
 
         constexpr SkColor kMinFpsColor = SK_ColorRED;
         constexpr SkColor kMaxFpsColor = SK_ColorGREEN;
@@ -228,9 +222,9 @@
 
         const SkColor color = colorBase.toSkColor();
 
-        auto buffers = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures);
+        auto buffers = draw(refreshIntFps, renderIntFps, color, transformHint, mFeatures);
         it = mBufferCache
-                     .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
+                     .try_emplace({refreshIntFps, renderIntFps, transformHint}, std::move(buffers))
                      .first;
     }
 
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 93ec36e..b2896f0 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -65,7 +65,7 @@
 
     using Buffers = std::vector<sp<GraphicBuffer>>;
 
-    static Buffers draw(int vsyncRate, int renderFps, SkColor, ui::Transform::RotationFlags,
+    static Buffers draw(int refreshRate, int renderFps, SkColor, ui::Transform::RotationFlags,
                         ftl::Flags<Features>);
     static void drawNumber(int number, int left, SkColor, SkCanvas&);
 
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 2b4e234..5add290 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -315,39 +315,15 @@
         return true;
     };
 
-    std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshots;
-    if (mFlinger.mLayerLifecycleManagerEnabled) {
-        auto filterFn = [&](const frontend::LayerSnapshot& snapshot,
-                            bool& outStopTraversal) -> bool {
-            const Rect bounds =
-                    frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
-                                                          snapshot.transparentRegionHint);
-            const ui::Transform transform = snapshot.geomLayerTransform;
-            return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform,
-                                 outStopTraversal);
-        };
-        getLayerSnapshots =
-                mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
-                                                         filterFn);
-    } else {
-        auto traverseLayers = [&](const LayerVector::Visitor& visitor) {
-            bool stopLayerFound = false;
-            auto filterVisitor = [&](Layer* layer) {
-                // We don't want to capture any layers beyond the stop layer
-                if (stopLayerFound) return;
-
-                if (!layerFilterFn(layer->getDebugName(), layer->getSequence(),
-                                   Rect(layer->getBounds()), layer->getTransform(),
-                                   stopLayerFound)) {
-                    return;
-                }
-                visitor(layer);
-            };
-            mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {},
-                                                filterVisitor);
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
-    }
+    auto filterFn = [&](const frontend::LayerSnapshot& snapshot, bool& outStopTraversal) -> bool {
+        const Rect bounds = frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
+                                                                  snapshot.transparentRegionHint);
+        const ui::Transform transform = snapshot.geomLayerTransform;
+        return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform,
+                             outStopTraversal);
+    };
+    auto getLayerSnapshotsFn =
+            mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, filterFn);
 
     std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr;
     if (mCachedBuffer && mCachedBuffer->getBuffer()->getWidth() == sampledBounds.getWidth() &&
@@ -372,17 +348,30 @@
     constexpr bool kGrayscale = false;
     constexpr bool kIsProtected = false;
 
-    if (const auto fenceResult =
-                mFlinger.captureScreenshot(SurfaceFlinger::RenderAreaBuilderVariant(
-                                                   std::in_place_type<DisplayRenderAreaBuilder>,
-                                                   sampledBounds, sampledBounds.getSize(),
-                                                   ui::Dataspace::V0_SRGB,
-                                                   kHintForSeamlessTransition,
-                                                   true /* captureSecureLayers */, displayWeak),
-                                           getLayerSnapshots, buffer, kRegionSampling, kGrayscale,
-                                           kIsProtected, nullptr)
+    SurfaceFlinger::RenderAreaBuilderVariant
+            renderAreaBuilder(std::in_place_type<DisplayRenderAreaBuilder>, sampledBounds,
+                              sampledBounds.getSize(), ui::Dataspace::V0_SRGB,
+                              kHintForSeamlessTransition, true /* captureSecureLayers */,
+                              displayWeak);
+
+    FenceResult fenceResult;
+    if (FlagManager::getInstance().single_hop_screenshot() &&
+        FlagManager::getInstance().ce_fence_promise()) {
+        std::vector<sp<LayerFE>> layerFEs;
+        auto displayState =
+                mFlinger.getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder,
+                                                                   getLayerSnapshotsFn, layerFEs);
+        fenceResult =
+                mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale,
+                                           kIsProtected, nullptr, displayState, layerFEs)
                         .get();
-        fenceResult.ok()) {
+    } else {
+        fenceResult =
+                mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, buffer,
+                                                 kRegionSampling, kGrayscale, kIsProtected, nullptr)
+                        .get();
+    }
+    if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
     }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 2c66492..be5ffbc 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -1066,6 +1066,11 @@
         ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
         ATRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
                               to_string(overrideFps).c_str(), uid);
+        if (ATRACE_ENABLED() && FlagManager::getInstance().trace_frame_rate_override()) {
+            std::stringstream ss;
+            ss << "FrameRateOverride " << uid;
+            ATRACE_INT(ss.str().c_str(), overrideFps.getIntValue());
+        }
         frameRateOverrides.emplace(uid, overrideFps);
     }
 
@@ -1636,7 +1641,7 @@
         case FrameRateCategory::Normal:
             return FpsRange{60_Hz, 120_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 120_Hz};
+            return FpsRange{48_Hz, 120_Hz};
         case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 60681a2..e669261 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -123,19 +123,22 @@
     promotePacesetterDisplay(pacesetterIdOpt);
 }
 
-void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+                                PhysicalDisplayId activeDisplayId) {
     auto schedulePtr =
             std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
                                             [this](PhysicalDisplayId id, bool enable) {
                                                 onHardwareVsyncRequest(id, enable);
                                             });
 
-    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
+    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr),
+                            activeDisplayId);
 }
 
 void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
                                         RefreshRateSelectorPtr selectorPtr,
-                                        VsyncSchedulePtr schedulePtr) {
+                                        VsyncSchedulePtr schedulePtr,
+                                        PhysicalDisplayId activeDisplayId) {
     demotePacesetterDisplay();
 
     auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) {
@@ -145,7 +148,7 @@
                                                        std::move(schedulePtr), mFeatures)
                                    .second;
 
-        return std::make_pair(promotePacesetterDisplayLocked(), isNew);
+        return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId), isNew);
     }();
 
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
@@ -158,7 +161,9 @@
     dispatchHotplug(displayId, Hotplug::Connected);
 }
 
-void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId activeDisplayId) {
+    LOG_ALWAYS_FATAL_IF(displayId == activeDisplayId, "Cannot unregister the active display!");
+
     dispatchHotplug(displayId, Hotplug::Disconnected);
 
     demotePacesetterDisplay();
@@ -173,7 +178,7 @@
         // headless virtual display.)
         LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!");
 
-        pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId);
     }
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
@@ -1113,8 +1118,10 @@
                                                 .emitEvent = !choice.consideredSignals.idle});
         }
 
-        frameRateOverridesChanged = updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
-
+        if (!FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+            frameRateOverridesChanged =
+                    updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
+        }
         if (mPolicy.modeOpt != modeOpt) {
             mPolicy.modeOpt = modeOpt;
             refreshRateChanged = true;
@@ -1129,6 +1136,12 @@
     if (refreshRateChanged) {
         mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
+
+    if (FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+        std::scoped_lock lock(mPolicyLock);
+        frameRateOverridesChanged =
+                updateFrameRateOverridesLocked(consideredSignals, mPolicy.modeOpt->fps);
+    }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
     }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ccaa05f..1a4aa79 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -101,9 +101,16 @@
     using ConstVsyncSchedulePtr = std::shared_ptr<const VsyncSchedule>;
     using VsyncSchedulePtr = std::shared_ptr<VsyncSchedule>;
 
-    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
+    // After registration/unregistration, `activeDisplayId` is promoted to pacesetter. Note that the
+    // active display is never unregistered, since hotplug disconnect never happens for activatable
+    // displays, i.e. a foldable's internal displays or otherwise the (internal or external) primary
+    // display.
+    // TODO: b/255635821 - Remove active display parameters.
+    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr,
+                         PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
-    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId, PhysicalDisplayId activeDisplayId)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void run();
 
@@ -390,8 +397,9 @@
     // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
     void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
 
-    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr)
-            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr,
+                                 PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
 
     struct Policy;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 85ce713..0644aca 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -360,7 +360,11 @@
     purgeTimelines(now);
 
     for (auto& timeline : mTimelines) {
-        if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
+        const bool isVsyncValid = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? timeline.isWithin(TimePoint::fromNs(vsync)) ==
+                        VsyncTimeline::VsyncOnTimeline::Unique
+                : timeline.validUntil() && timeline.validUntil()->ns() > vsync;
+        if (isVsyncValid) {
             return timeline.isVSyncInPhase(model, vsync, frameRate);
         }
     }
@@ -395,8 +399,14 @@
         mLastCommittedVsync = TimePoint::fromNs(0);
 
     } else {
-        mTimelines.back().freeze(
-                TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+            // We need to freeze the timeline at the committed vsync so that we don't
+            // overshoot the deadline.
+            mTimelines.back().freeze(mLastCommittedVsync);
+        } else {
+            mTimelines.back().freeze(
+                    TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        }
     }
     mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
     purgeTimelines(TimePoint::fromNs(mClock->now()));
@@ -611,7 +621,10 @@
 
     while (mTimelines.size() > 1) {
         const auto validUntilOpt = mTimelines.front().validUntil();
-        if (validUntilOpt && *validUntilOpt < now) {
+        const bool isTimelineOutDated = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? mTimelines.front().isWithin(now) == VsyncTimeline::VsyncOnTimeline::Outside
+                : validUntilOpt && *validUntilOpt < now;
+        if (isTimelineOutDated) {
             mTimelines.pop_front();
         } else {
             break;
@@ -660,9 +673,12 @@
             vsyncTime += missedVsync.fixup.ns();
             ATRACE_FORMAT_INSTANT("lastFrameMissed");
         } else if (mightBackpressure && lastVsyncOpt) {
-            // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
-            // first before trying to use it.
-            lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            if (!FlagManager::getInstance().vrr_bugfix_24q4()) {
+                // lastVsyncOpt does not need to be corrected with the new rate, and
+                // it should be used as is to avoid skipping a frame when changing rates are
+                // aligned at vsync time.
+                lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            }
             const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
             if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
                 // avoid a duplicate vsync
@@ -681,7 +697,10 @@
     }
 
     ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
-    if (mValidUntil && vsyncTime > mValidUntil->ns()) {
+    const bool isVsyncInvalid = FlagManager::getInstance().vrr_bugfix_24q4()
+            ? isWithin(TimePoint::fromNs(vsyncTime)) == VsyncOnTimeline::Outside
+            : mValidUntil && vsyncTime > mValidUntil->ns();
+    if (isVsyncInvalid) {
         ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
                               static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
         return std::nullopt;
@@ -737,7 +756,9 @@
         return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
     };
 
-    Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns());
+    Fps displayFps = !FlagManager::getInstance().vrr_bugfix_24q4() && mRenderRateOpt
+            ? *mRenderRateOpt
+            : Fps::fromPeriodNsecs(mIdealPeriod.ns());
     const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate);
     const auto now = TimePoint::now();
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 8ce61d8..66a7d71 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -106,6 +106,24 @@
         void shiftVsyncSequence(Duration phase);
         void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
 
+        enum class VsyncOnTimeline {
+            Unique,  // Within timeline, not shared with next timeline.
+            Shared,  // Within timeline, shared with next timeline.
+            Outside, // Outside of the timeline.
+        };
+        VsyncOnTimeline isWithin(TimePoint vsync) {
+            const auto threshold = mIdealPeriod.ns() / 2;
+            if (!mValidUntil || vsync.ns() < mValidUntil->ns() - threshold) {
+                // if mValidUntil is absent then timeline is not frozen and
+                // vsync should be unique to that timeline.
+                return VsyncOnTimeline::Unique;
+            }
+            if (vsync.ns() > mValidUntil->ns() + threshold) {
+                return VsyncOnTimeline::Outside;
+            }
+            return VsyncOnTimeline::Shared;
+        }
+
     private:
         nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
         VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5f81cd4..fd4bd11 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,6 +40,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
@@ -813,8 +814,24 @@
                 .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else {
         const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK;
+// TODO: b/341728634 - Clean up conditional compilation.
+// Note: this guard in particular must check e.g.
+// COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE directly (instead of calling e.g.
+// COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE)) because that macro is undefined
+// in the libsurfaceflingerflags_test variant of com_android_graphics_surfaceflinger_flags.h, which
+// is used by layertracegenerator (which also needs SurfaceFlinger.cpp). :)
+#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE || \
+        COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_FORCE_COMPILE_GRAPHITE_RENDERENGINE
         const bool useGraphite = FlagManager::getInstance().graphite_renderengine() &&
                 renderengine::RenderEngine::canSupport(kVulkan);
+#else
+        const bool useGraphite = false;
+        if (FlagManager::getInstance().graphite_renderengine()) {
+            ALOGE("RenderEngine's Graphite Skia backend was requested with the "
+                  "debug.renderengine.graphite system property, but it is not compiled in this "
+                  "build! Falling back to Ganesh backend selection logic.");
+        }
+#endif
         const bool useVulkan = useGraphite ||
                 (FlagManager::getInstance().vulkan_renderengine() &&
                  renderengine::RenderEngine::canSupport(kVulkan));
@@ -874,8 +891,12 @@
     }
 
     mCompositionEngine->setTimeStats(mTimeStats);
+
     mCompositionEngine->setHwComposer(getFactory().createHWComposer(mHwcServiceName));
-    mCompositionEngine->getHwComposer().setCallback(*this);
+    auto& composer = mCompositionEngine->getHwComposer();
+    composer.setCallback(*this);
+    mDisplayModeController.setHwComposer(&composer);
+
     ClientCache::getInstance().setRenderEngine(&getRenderEngine());
 
     mHasReliablePresentFences =
@@ -914,6 +935,20 @@
     // initializing the Scheduler after configureLocked, once decoupled from DisplayDevice.
     initScheduler(display);
 
+    // Start listening after creating the Scheduler, since the listener calls into it.
+    mDisplayModeController.setActiveModeListener(
+            display::DisplayModeController::ActiveModeListener::make(
+                    [this](PhysicalDisplayId displayId, Fps vsyncRate, Fps renderRate) {
+                        // This callback cannot lock mStateLock, as some callers already lock it.
+                        // Instead, switch context to the main thread.
+                        static_cast<void>(mScheduler->schedule([=,
+                                                                this]() FTL_FAKE_GUARD(mStateLock) {
+                            if (const auto display = getDisplayDeviceLocked(displayId)) {
+                                display->updateRefreshRateOverlayRate(vsyncRate, renderRate);
+                            }
+                        }));
+                    }));
+
     mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) {
         auto snapshot = perfetto::protos::LayersSnapshotProto{};
         mScheduler
@@ -1279,19 +1314,19 @@
 
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
-    const auto display = getDisplayDeviceLocked(displayId);
-    if (!display) {
-        ALOGW("%s: display is no longer valid", __func__);
-        return;
-    }
-
     const bool emitEvent = desiredMode.emitEvent;
 
-    switch (display->setDesiredMode(std::move(desiredMode))) {
-        case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
-            // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
-            mScheduler->setRenderRate(displayId, display->refreshRateSelector().getActiveMode().fps,
-                                      /*applyImmediately*/ true);
+    using DesiredModeAction = display::DisplayModeController::DesiredModeAction;
+
+    switch (mDisplayModeController.setDesiredMode(displayId, std::move(desiredMode))) {
+        case DesiredModeAction::InitiateDisplayModeSwitch: {
+            const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId);
+            if (!selectorPtr) break;
+
+            const Fps renderRate = selectorPtr->getActiveMode().fps;
+
+            // DisplayModeController::setDesiredMode updated the render rate, so inform Scheduler.
+            mScheduler->setRenderRate(displayId, renderRate, true /* applyImmediately */);
 
             // Schedule a new frame to initiate the display mode switch.
             scheduleComposite(FrameHint::kNone);
@@ -1311,7 +1346,8 @@
 
             mScheduler->setModeChangePending(true);
             break;
-        case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
+        }
+        case DesiredModeAction::InitiateRenderRateSwitch:
             mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false);
 
             if (displayId == mActiveDisplayId) {
@@ -1322,7 +1358,7 @@
                 dispatchDisplayModeChangeEvent(displayId, mode);
             }
             break;
-        case DisplayDevice::DesiredModeAction::None:
+        case DesiredModeAction::None:
             break;
     }
 }
@@ -1378,11 +1414,10 @@
     return future.get();
 }
 
-void SurfaceFlinger::finalizeDisplayModeChange(DisplayDevice& display) {
-    const auto displayId = display.getPhysicalId();
+void SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
-    const auto pendingModeOpt = display.getPendingMode();
+    const auto pendingModeOpt = mDisplayModeController.getPendingMode(displayId);
     if (!pendingModeOpt) {
         // There is no pending mode change. This can happen if the active
         // display changed and the mode change happened on a different display.
@@ -1391,8 +1426,12 @@
 
     const auto& activeMode = pendingModeOpt->mode;
 
-    if (display.getActiveMode().modePtr->getResolution() != activeMode.modePtr->getResolution()) {
-        auto& state = mCurrentState.displays.editValueFor(display.getDisplayToken());
+    if (const auto oldResolution =
+                mDisplayModeController.getActiveMode(displayId).modePtr->getResolution();
+        oldResolution != activeMode.modePtr->getResolution()) {
+        Mutex::Autolock lock(mStateLock);
+
+        auto& state = mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId));
         // We need to generate new sequenceId in order to recreate the display (and this
         // way the framebuffer).
         state.sequenceId = DisplayDeviceState{}.sequenceId;
@@ -1403,8 +1442,8 @@
         return;
     }
 
-    display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
-                               activeMode.fps);
+    mDisplayModeController.finalizeModeChange(displayId, activeMode.modePtr->getId(),
+                                              activeMode.modePtr->getVsyncRate(), activeMode.fps);
 
     if (displayId == mActiveDisplayId) {
         mScheduler->updatePhaseConfiguration(activeMode.fps);
@@ -1415,21 +1454,20 @@
     }
 }
 
-void SurfaceFlinger::dropModeRequest(const sp<DisplayDevice>& display) {
-    display->clearDesiredMode();
-    if (display->getPhysicalId() == mActiveDisplayId) {
+void SurfaceFlinger::dropModeRequest(PhysicalDisplayId displayId) {
+    mDisplayModeController.clearDesiredMode(displayId);
+    if (displayId == mActiveDisplayId) {
         // TODO(b/255635711): Check for pending mode changes on other displays.
         mScheduler->setModeChangePending(false);
     }
 }
 
-void SurfaceFlinger::applyActiveMode(const sp<DisplayDevice>& display) {
-    const auto activeModeOpt = display->getDesiredMode();
+void SurfaceFlinger::applyActiveMode(PhysicalDisplayId displayId) {
+    const auto activeModeOpt = mDisplayModeController.getDesiredMode(displayId);
     auto activeModePtr = activeModeOpt->mode.modePtr;
-    const auto displayId = activeModePtr->getPhysicalDisplayId();
     const auto renderFps = activeModeOpt->mode.fps;
 
-    dropModeRequest(display);
+    dropModeRequest(displayId);
 
     constexpr bool kAllowToEnable = true;
     mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
@@ -1445,11 +1483,8 @@
 
     std::optional<PhysicalDisplayId> displayToUpdateImmediately;
 
-    for (const auto& [id, physical] : mPhysicalDisplays) {
-        const auto display = getDisplayDeviceLocked(id);
-        if (!display) continue;
-
-        auto desiredModeOpt = display->getDesiredMode();
+    for (const auto& [displayId, physical] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) {
+        auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
         if (!desiredModeOpt) {
             continue;
         }
@@ -1466,19 +1501,21 @@
         ALOGV("%s changing active mode to %d(%s) for display %s", __func__,
               ftl::to_underlying(desiredModeId),
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
-              to_string(display->getId()).c_str());
+              to_string(displayId).c_str());
 
         if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) &&
-            display->getActiveMode() == desiredModeOpt->mode) {
-            applyActiveMode(display);
+            mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+            applyActiveMode(displayId);
             continue;
         }
 
+        const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId);
+
         // Desired active mode was set, it is different than the mode currently in use, however
         // allowed modes might have changed by the time we process the refresh.
         // Make sure the desired mode is still allowed
-        if (!display->refreshRateSelector().isModeAllowed(desiredModeOpt->mode)) {
-            dropModeRequest(display);
+        if (!selectorPtr->isModeAllowed(desiredModeOpt->mode)) {
+            dropModeRequest(displayId);
             continue;
         }
 
@@ -1488,11 +1525,12 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
-        if (!display->initiateModeChange(std::move(*desiredModeOpt), constraints, outTimeline)) {
+        if (!mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
+                                                       constraints, outTimeline)) {
             continue;
         }
 
-        display->refreshRateSelector().onModeChangeInitiated();
+        selectorPtr->onModeChangeInitiated();
         mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline);
 
         if (outTimeline.refreshRequired) {
@@ -1501,17 +1539,18 @@
             // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange`
             // for all displays. This was only needed when the loop iterated over `mDisplays` rather
             // than `mPhysicalDisplays`.
-            displayToUpdateImmediately = display->getPhysicalId();
+            displayToUpdateImmediately = displayId;
         }
     }
 
     if (displayToUpdateImmediately) {
-        const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately);
-        finalizeDisplayModeChange(*display);
+        const auto displayId = *displayToUpdateImmediately;
+        finalizeDisplayModeChange(displayId);
 
-        const auto desiredModeOpt = display->getDesiredMode();
-        if (desiredModeOpt && display->getActiveMode() == desiredModeOpt->mode) {
-            applyActiveMode(display);
+        const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
+        if (desiredModeOpt &&
+            mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+            applyActiveMode(displayId);
         }
     }
 }
@@ -2259,8 +2298,10 @@
                         getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos
                                                                         : data.vsyncPeriodNanos);
                 ATRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
-                display->updateRefreshRateOverlayRate(refreshRate, display->getActiveMode().fps,
-                                                      /* showRefreshRate */ true);
+
+                const auto renderRate = mDisplayModeController.getActiveMode(*displayIdOpt).fps;
+                constexpr bool kSetByHwc = true;
+                display->updateRefreshRateOverlayRate(refreshRate, renderRate, kSetByHwc);
             }
         }
     }));
@@ -2568,33 +2609,18 @@
 
     // If a mode set is pending and the fence hasn't fired yet, wait for the next commit.
     if (std::any_of(frameTargets.begin(), frameTargets.end(),
-                    [this](const auto& pair) FTL_FAKE_GUARD(mStateLock)
-                            FTL_FAKE_GUARD(kMainThreadContext) {
-                                if (!pair.second->isFramePending()) return false;
-
-                                if (const auto display = getDisplayDeviceLocked(pair.first)) {
-                                    return display->isModeSetPending();
-                                }
-
-                                return false;
-                            })) {
+                    [this](const auto& pair) FTL_FAKE_GUARD(kMainThreadContext) {
+                        const auto [displayId, target] = pair;
+                        return target->isFramePending() &&
+                                mDisplayModeController.isModeSetPending(displayId);
+                    })) {
         mScheduler->scheduleFrame();
         return false;
     }
 
-    {
-        Mutex::Autolock lock(mStateLock);
-
-        for (const auto [id, target] : frameTargets) {
-            // TODO(b/241285876): This is `nullptr` when the DisplayDevice is about to be removed in
-            // this commit, since the PhysicalDisplay has already been removed. Rather than checking
-            // for `nullptr` below, change Scheduler::onFrameSignal to filter out the FrameTarget of
-            // the removed display.
-            const auto display = getDisplayDeviceLocked(id);
-
-            if (display && display->isModeSetPending()) {
-                finalizeDisplayModeChange(*display);
-            }
+    for (const auto [displayId, _] : frameTargets) {
+        if (mDisplayModeController.isModeSetPending(displayId)) {
+            finalizeDisplayModeChange(displayId);
         }
     }
 
@@ -2629,8 +2655,8 @@
         mPowerAdvisor->setFrameDelay(frameDelay);
         mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration);
 
-        const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
-        const Period idealVsyncPeriod = display->getActiveMode().fps.getPeriod();
+        const Period idealVsyncPeriod =
+                mDisplayModeController.getActiveMode(pacesetterId).fps.getPeriod();
         mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod);
     }
 
@@ -2693,9 +2719,10 @@
                                                         ? &mLayerHierarchyBuilder.getHierarchy()
                                                         : nullptr,
                                                 updateAttachedChoreographer);
-        initiateDisplayModeChanges();
     }
 
+    initiateDisplayModeChanges();
+
     updateCursorAsync();
     if (!mustComposite) {
         updateInputFlinger(vsyncId, pacesetterFrameTarget.frameBeginTime());
@@ -3741,7 +3768,8 @@
 
     if (const auto& physical = state.physical) {
         const auto& mode = *physical->activeMode;
-        display->setActiveMode(mode.getId(), mode.getVsyncRate(), mode.getVsyncRate());
+        mDisplayModeController.setActiveMode(physical->id, mode.getId(), mode.getVsyncRate(),
+                                             mode.getVsyncRate());
     }
 
     display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack));
@@ -3827,7 +3855,8 @@
         ftl::FakeGuard guard(kMainThreadContext);
 
         // For hotplug reconnect, renew the registration since display modes have been reloaded.
-        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                    mActiveDisplayId);
     }
 
     if (display->isVirtual()) {
@@ -3866,7 +3895,7 @@
         if (display->isVirtual()) {
             releaseVirtualDisplay(display->getVirtualId());
         } else {
-            mScheduler->unregisterDisplay(display->getPhysicalId());
+            mScheduler->unregisterDisplay(display->getPhysicalId(), mActiveDisplayId);
         }
     }
 
@@ -3920,7 +3949,9 @@
 
             // TODO(b/175678251) Call a listener instead.
             if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                mScheduler->resetPhaseConfiguration(display->getActiveMode().fps);
+                const Fps refreshRate =
+                        mDisplayModeController.getActiveMode(display->getPhysicalId()).fps;
+                mScheduler->resetPhaseConfiguration(refreshRate);
             }
         }
         return;
@@ -4476,7 +4507,8 @@
                                              getFactory(), activeRefreshRate, *mTimeStats);
 
     // The pacesetter must be registered before EventThread creation below.
-    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                mActiveDisplayId);
     if (FlagManager::getInstance().vrr_config()) {
         mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps,
                                   /*applyImmediately*/ true);
@@ -5137,7 +5169,7 @@
 
 status_t SurfaceFlinger::setTransactionState(
         const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+        Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
         InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
         const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
         const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
@@ -5152,7 +5184,7 @@
         composerState.state.sanitize(permissions);
     }
 
-    for (DisplayState display : displays) {
+    for (DisplayState& display : displays) {
         display.sanitize(permissions);
     }
 
@@ -5618,10 +5650,7 @@
         layer->setInputInfo(*s.windowInfoHandle->getInfo());
         flags |= eTraversalNeeded;
     }
-    std::optional<nsecs_t> dequeueBufferTimestamp;
     if (what & layer_state_t::eMetadataChanged) {
-        dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
-
         if (const int32_t gameMode = s.metadata.getInt32(gui::METADATA_GAME_MODE, -1);
             gameMode != -1) {
             // The transaction will be received on the Task layer and needs to be applied to all
@@ -5763,8 +5792,7 @@
 
     if (what & layer_state_t::eBufferChanged) {
         if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
-                             desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
-                             frameTimelineInfo)) {
+                             desiredPresentTime, isAutoTimestamp, frameTimelineInfo)) {
             flags |= eTraversalNeeded;
         }
     } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
@@ -5850,10 +5878,6 @@
     if (what & layer_state_t::eProducerDisconnect) {
         layer->onDisconnect();
     }
-    std::optional<nsecs_t> dequeueBufferTimestamp;
-    if (what & layer_state_t::eMetadataChanged) {
-        dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
-    }
 
     std::vector<sp<CallbackHandle>> callbackHandles;
     if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) {
@@ -5900,8 +5924,7 @@
         }
         layer->setTransformHint(transformHint);
         if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
-                             desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
-                             frameTimelineInfo)) {
+                             desiredPresentTime, isAutoTimestamp, frameTimelineInfo)) {
             flags |= eTraversalNeeded;
         }
         mLayersWithQueuedFrames.emplace(layer);
@@ -6368,15 +6391,23 @@
         return NO_ERROR;
     }
 
-    // Traversal of drawing state must happen on the main thread.
-    // Otherwise, SortedVector may have shared ownership during concurrent
-    // traversals, which can result in use-after-frees.
+    // Collect debug data from main thread
     std::string compositionLayers;
     mScheduler
             ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
                 dumpVisibleFrontEnd(compositionLayers);
             })
             .get();
+    // get window info listener data without the state lock
+    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
+    compositionLayers.append("Window Infos:\n");
+    StringAppendF(&compositionLayers, "  max send vsync id: %" PRId64 "\n",
+                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
+    StringAppendF(&compositionLayers, "  max send delay (ns): %" PRId64 " ns\n",
+                  windowInfosDebug.maxSendDelayDuration);
+    StringAppendF(&compositionLayers, "  unsent messages: %zu\n",
+                  windowInfosDebug.pendingMessageCount);
+    compositionLayers.append("\n");
     dumpAll(args, compositionLayers, result);
     write(fd, result.c_str(), result.size());
     return NO_ERROR;
@@ -6959,15 +6990,6 @@
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
-
-    result.append("Window Infos:\n");
-    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
-    StringAppendF(&result, "  max send vsync id: %" PRId64 "\n",
-                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
-    StringAppendF(&result, "  max send delay (ns): %" PRId64 " ns\n",
-                  windowInfosDebug.maxSendDelayDuration);
-    StringAppendF(&result, "  unsent messages: %zu\n", windowInfosDebug.pendingMessageCount);
-    result.append("\n");
 }
 
 mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
@@ -7667,9 +7689,10 @@
         if (!display->isRefreshRateOverlayEnabled()) return;
 
         const auto desiredModeIdOpt =
-                display->getDesiredMode().transform([](const display::DisplayModeRequest& request) {
-                    return request.mode.modePtr->getId();
-                });
+                mDisplayModeController.getDesiredMode(display->getPhysicalId())
+                        .transform([](const display::DisplayModeRequest& request) {
+                            return request.mode.modePtr->getId();
+                        });
 
         const bool timerExpired = mKernelIdleTimerEnabled && expired;
 
@@ -7846,14 +7869,13 @@
 
 namespace {
 
-ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayDevice* display,
+ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace,
+                                const compositionengine::impl::OutputCompositionState& state,
                                 bool capturingHdrLayers, bool hintForSeamlessTransition) {
-    if (requestedDataspace != ui::Dataspace::UNKNOWN || display == nullptr) {
+    if (requestedDataspace != ui::Dataspace::UNKNOWN) {
         return requestedDataspace;
     }
 
-    const auto& state = display->getCompositionDisplay()->getState();
-
     const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode);
 
     // TODO: Enable once HDR screenshots are ready.
@@ -7933,23 +7955,14 @@
         }
     }
 
-    GetLayerSnapshotsFunction getLayerSnapshots;
-    if (mLayerLifecycleManagerEnabled) {
-        getLayerSnapshots =
-                getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds));
-    } else {
-        auto traverseLayers = [this, args, excludeLayerIds,
-                               layerStack](const LayerVector::Visitor& visitor) {
-            traverseLayersInLayerStack(layerStack, args.uid, std::move(excludeLayerIds), visitor);
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
-    }
+    GetLayerSnapshotsFunction getLayerSnapshotsFn =
+            getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds));
 
     captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
                                                  args.sourceCrop, reqSize, args.dataspace,
                                                  args.hintForSeamlessTransition,
                                                  args.captureSecureLayers, displayWeak),
-                        getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected,
+                        getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected,
                         args.grayscale, captureListener);
 }
 
@@ -7986,16 +7999,9 @@
         return;
     }
 
-    GetLayerSnapshotsFunction getLayerSnapshots;
-    if (mLayerLifecycleManagerEnabled) {
-        getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
-                                                            /*snapshotFilterFn=*/nullptr);
-    } else {
-        auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
-            traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, visitor);
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
-    }
+    GetLayerSnapshotsFunction getLayerSnapshotsFn =
+            getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
+                                            /*snapshotFilterFn=*/nullptr);
 
     if (captureListener == nullptr) {
         ALOGE("capture screen must provide a capture listener callback");
@@ -8010,7 +8016,7 @@
                                                  Rect(), size, args.dataspace,
                                                  args.hintForSeamlessTransition,
                                                  false /* captureSecureLayers */, displayWeak),
-                        getLayerSnapshots, size, args.pixelFormat, kAllowProtected, kGrayscale,
+                        getLayerSnapshotsFn, size, args.pixelFormat, kAllowProtected, kGrayscale,
                         captureListener);
 }
 
@@ -8092,42 +8098,16 @@
         return;
     }
 
-    GetLayerSnapshotsFunction getLayerSnapshots;
-    if (mLayerLifecycleManagerEnabled) {
-        std::optional<FloatRect> parentCrop = std::nullopt;
-        if (args.childrenOnly) {
-            parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
-                                        : crop.toFloatRect();
-        }
-
-        getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid,
-                                                            std::move(excludeLayerIds),
-                                                            args.childrenOnly, parentCrop);
-    } else {
-        auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) {
-            parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
-                if (!layer->isVisible()) {
-                    return;
-                } else if (args.childrenOnly && layer == parent.get()) {
-                    return;
-                } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
-                    return;
-                }
-
-                auto p = sp<Layer>::fromExisting(layer);
-                while (p != nullptr) {
-                    if (excludeLayerIds.count(p->sequence) != 0) {
-                        return;
-                    }
-                    p = p->getParent();
-                }
-
-                visitor(layer);
-            });
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    std::optional<FloatRect> parentCrop = std::nullopt;
+    if (args.childrenOnly) {
+        parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
+                                    : crop.toFloatRect();
     }
 
+    GetLayerSnapshotsFunction getLayerSnapshotsFn =
+            getLayerSnapshotsForScreenshots(parent->sequence, args.uid, std::move(excludeLayerIds),
+                                            args.childrenOnly, parentCrop);
+
     if (captureListener == nullptr) {
         ALOGD("capture screen must provide a capture listener callback");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
@@ -8138,7 +8118,7 @@
                                                  reqSize, dataspace, args.captureSecureLayers,
                                                  args.hintForSeamlessTransition, parent,
                                                  args.childrenOnly),
-                        getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected,
+                        getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected,
                         args.grayscale, captureListener);
 }
 
@@ -8153,12 +8133,15 @@
     owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
 }
 
-bool SurfaceFlinger::layersHasProtectedLayer(
-        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
+// Loop over all visible layers to see whether there's any protected layer. A protected layer is
+// typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer.
+// A protected layer has no implication on whether it's secure, which is explicitly set by
+// application to avoid being screenshot or drawn via unsecure display.
+bool SurfaceFlinger::layersHasProtectedLayer(const std::vector<sp<LayerFE>>& layers) const {
     bool protectedLayerFound = false;
-    for (auto& [_, layerFe] : layers) {
+    for (auto& layerFE : layers) {
         protectedLayerFound |=
-                (layerFe->mSnapshot->isVisible && layerFe->mSnapshot->hasProtectedContent);
+                (layerFE->mSnapshot->isVisible && layerFE->mSnapshot->hasProtectedContent);
         if (protectedLayerFound) {
             break;
         }
@@ -8166,8 +8149,28 @@
     return protectedLayerFound;
 }
 
+// Getting layer snapshots and display should take place on main thread.
+// Accessing display requires mStateLock, and contention for this lock
+// is reduced when grabbed from the main thread, thus also reducing
+// risk of deadlocks.
+std::optional<SurfaceFlinger::OutputCompositionState>
+SurfaceFlinger::getDisplayAndLayerSnapshotsFromMainThread(
+        RenderAreaBuilderVariant& renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
+        std::vector<sp<LayerFE>>& layerFEs) {
+    return mScheduler
+            ->schedule([=, this, &renderAreaBuilder, &layerFEs]() REQUIRES(kMainThreadContext) {
+                auto layers = getLayerSnapshotsFn();
+                for (auto& [layer, layerFE] : layers) {
+                    attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+                }
+                layerFEs = extractLayerFEs(layers);
+                return getDisplayStateFromRenderAreaBuilder(renderAreaBuilder);
+            })
+            .get();
+}
+
 void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder,
-                                         GetLayerSnapshotsFunction getLayerSnapshots,
+                                         GetLayerSnapshotsFunction getLayerSnapshotsFn,
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
                                          bool allowProtected, bool grayscale,
                                          const sp<IScreenCaptureListener>& captureListener) {
@@ -8181,74 +8184,185 @@
         return;
     }
 
-    // Loop over all visible layers to see whether there's any protected layer. A protected layer is
-    // typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer.
-    // A protected layer has no implication on whether it's secure, which is explicitly set by
-    // application to avoid being screenshot or drawn via unsecure display.
-    const bool supportsProtected = getRenderEngine().supportsProtectedContent();
-    bool hasProtectedLayer = false;
-    if (allowProtected && supportsProtected) {
-        auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get();
-        hasProtectedLayer = layersHasProtectedLayer(layers);
-    }
-    const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
-    const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
-            GRALLOC_USAGE_HW_TEXTURE |
-            (isProtected ? GRALLOC_USAGE_PROTECTED
-                         : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
-    sp<GraphicBuffer> buffer =
-            getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
-                                             static_cast<android_pixel_format>(reqPixelFormat),
-                                             1 /* layerCount */, usage, "screenshot");
+    if (FlagManager::getInstance().single_hop_screenshot() &&
+        FlagManager::getInstance().ce_fence_promise()) {
+        std::vector<sp<LayerFE>> layerFEs;
+        auto displayState =
+                getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn,
+                                                          layerFEs);
 
-    const status_t bufferStatus = buffer->initCheck();
-    if (bufferStatus != OK) {
-        // Animations may end up being really janky, but don't crash here.
-        // Otherwise an irreponsible process may cause an SF crash by allocating
-        // too much.
-        ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
-        invokeScreenCaptureError(bufferStatus, captureListener);
-        return;
+        const bool supportsProtected = getRenderEngine().supportsProtectedContent();
+        bool hasProtectedLayer = false;
+        if (allowProtected && supportsProtected) {
+            hasProtectedLayer = layersHasProtectedLayer(layerFEs);
+        }
+        const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
+        const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
+                GRALLOC_USAGE_HW_TEXTURE |
+                (isProtected ? GRALLOC_USAGE_PROTECTED
+                             : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+        sp<GraphicBuffer> buffer =
+                getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
+                                                 static_cast<android_pixel_format>(reqPixelFormat),
+                                                 1 /* layerCount */, usage, "screenshot");
+
+        const status_t bufferStatus = buffer->initCheck();
+        if (bufferStatus != OK) {
+            // Animations may end up being really janky, but don't crash here.
+            // Otherwise an irreponsible process may cause an SF crash by allocating
+            // too much.
+            ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
+            invokeScreenCaptureError(bufferStatus, captureListener);
+            return;
+        }
+        const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+                renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             WRITEABLE);
+        auto futureFence =
+                captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */, grayscale,
+                                  isProtected, captureListener, displayState, layerFEs);
+        futureFence.get();
+
+    } else {
+        const bool supportsProtected = getRenderEngine().supportsProtectedContent();
+        bool hasProtectedLayer = false;
+        if (allowProtected && supportsProtected) {
+            auto layers = mScheduler->schedule([=]() { return getLayerSnapshotsFn(); }).get();
+            hasProtectedLayer = layersHasProtectedLayer(extractLayerFEs(layers));
+        }
+        const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
+        const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
+                GRALLOC_USAGE_HW_TEXTURE |
+                (isProtected ? GRALLOC_USAGE_PROTECTED
+                             : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+        sp<GraphicBuffer> buffer =
+                getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
+                                                 static_cast<android_pixel_format>(reqPixelFormat),
+                                                 1 /* layerCount */, usage, "screenshot");
+
+        const status_t bufferStatus = buffer->initCheck();
+        if (bufferStatus != OK) {
+            // Animations may end up being really janky, but don't crash here.
+            // Otherwise an irreponsible process may cause an SF crash by allocating
+            // too much.
+            ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
+            invokeScreenCaptureError(bufferStatus, captureListener);
+            return;
+        }
+        const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+                renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             WRITEABLE);
+        auto futureFence = captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, texture,
+                                                   false /* regionSampling */, grayscale,
+                                                   isProtected, captureListener);
+        futureFence.get();
     }
-    const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
-            renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
-                                                 renderengine::impl::ExternalTexture::Usage::
-                                                         WRITEABLE);
-    auto futureFence =
-            captureScreenshot(renderAreaBuilder, getLayerSnapshots, texture,
-                              false /* regionSampling */, grayscale, isProtected, captureListener);
-    futureFence.get();
 }
 
-ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
-        RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshots,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
-
-    auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
-                                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
-        // LayerSnapshots must be obtained from the main thread.
-        auto layers = getLayerSnapshots();
-
+std::optional<SurfaceFlinger::OutputCompositionState>
+SurfaceFlinger::getDisplayStateFromRenderAreaBuilder(RenderAreaBuilderVariant& renderAreaBuilder) {
+    sp<const DisplayDevice> display = nullptr;
+    {
+        Mutex::Autolock lock(mStateLock);
         if (auto* layerRenderAreaBuilder =
                     std::get_if<LayerRenderAreaBuilder>(&renderAreaBuilder)) {
             // LayerSnapshotBuilder should only be accessed from the main thread.
-            frontend::LayerSnapshot* snapshot =
+            const frontend::LayerSnapshot* snapshot =
                     mLayerSnapshotBuilder.getSnapshot(layerRenderAreaBuilder->layer->getSequence());
             if (!snapshot) {
                 ALOGW("Couldn't find layer snapshot for %d",
                       layerRenderAreaBuilder->layer->getSequence());
             } else {
                 layerRenderAreaBuilder->setLayerSnapshot(*snapshot);
+                display = findDisplay(
+                        [layerStack = snapshot->outputFilter.layerStack](const auto& display) {
+                            return display.getLayerStack() == layerStack;
+                        });
             }
+        } else if (auto* displayRenderAreaBuilder =
+                           std::get_if<DisplayRenderAreaBuilder>(&renderAreaBuilder)) {
+            display = displayRenderAreaBuilder->displayWeak.promote();
         }
 
+        if (display == nullptr) {
+            display = getDefaultDisplayDeviceLocked();
+        }
+
+        if (display != nullptr) {
+            return std::optional{display->getCompositionDisplay()->getState()};
+        }
+    }
+    return std::nullopt;
+}
+
+std::vector<sp<LayerFE>> SurfaceFlinger::extractLayerFEs(
+        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
+    std::vector<sp<LayerFE>> layerFEs;
+    layerFEs.reserve(layers.size());
+    for (const auto& [_, layerFE] : layers) {
+        layerFEs.push_back(layerFE);
+    }
+    return layerFEs;
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
+        const RenderAreaBuilderVariant& renderAreaBuilder,
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+        std::optional<OutputCompositionState>& displayState, std::vector<sp<LayerFE>>& layerFEs) {
+    ATRACE_CALL();
+
+    ScreenCaptureResults captureResults;
+    std::unique_ptr<const RenderArea> renderArea =
+            std::visit([](auto&& arg) -> std::unique_ptr<RenderArea> { return arg.build(); },
+                       renderAreaBuilder);
+
+    if (!renderArea) {
+        ALOGW("Skipping screen capture because of invalid render area.");
+        if (captureListener) {
+            captureResults.fenceResult = base::unexpected(NO_MEMORY);
+            captureListener->onScreenCaptureCompleted(captureResults);
+        }
+        return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
+    }
+
+    // Empty vector needed to pass into renderScreenImpl for legacy path
+    std::vector<std::pair<Layer*, sp<android::LayerFE>>> layers;
+    ftl::SharedFuture<FenceResult> renderFuture =
+            renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected,
+                             captureResults, displayState, layers, layerFEs);
+
+    if (captureListener) {
+        // Defer blocking on renderFuture back to the Binder thread.
+        return ftl::Future(std::move(renderFuture))
+                .then([captureListener, captureResults = std::move(captureResults)](
+                              FenceResult fenceResult) mutable -> FenceResult {
+                    captureResults.fenceResult = std::move(fenceResult);
+                    captureListener->onScreenCaptureCompleted(captureResults);
+                    return base::unexpected(NO_ERROR);
+                })
+                .share();
+    }
+    return renderFuture;
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshotLegacy(
+        RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
+    ATRACE_CALL();
+
+    auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
+                                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+        auto layers = getLayerSnapshotsFn();
         if (FlagManager::getInstance().ce_fence_promise()) {
             for (auto& [layer, layerFE] : layers) {
                 attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
             }
         }
+        auto displayState = getDisplayStateFromRenderAreaBuilder(renderAreaBuilder);
 
         ScreenCaptureResults captureResults;
         std::unique_ptr<const RenderArea> renderArea =
@@ -8264,9 +8378,10 @@
             return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
         }
 
+        auto layerFEs = extractLayerFEs(layers);
         ftl::SharedFuture<FenceResult> renderFuture =
                 renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale,
-                                 isProtected, captureResults, layers);
+                                 isProtected, captureResults, displayState, layers, layerFEs);
 
         if (captureListener) {
             // Defer blocking on renderFuture back to the Binder thread.
@@ -8296,22 +8411,14 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::unique_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) {
-    auto layers = getLayerSnapshots();
-    return renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected,
-                            captureResults, layers);
-}
-
-ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
         std::unique_ptr<const RenderArea> renderArea,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
-        std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) {
+        std::optional<OutputCompositionState>& displayState,
+        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers, std::vector<sp<LayerFE>>& layerFEs) {
     ATRACE_CALL();
 
-    for (auto& [_, layerFE] : layers) {
+    for (auto& layerFE : layerFEs) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
         captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure);
         captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
@@ -8334,78 +8441,54 @@
     const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() &&
             !renderArea->getHintForSeamlessTransition();
 
-    {
-        Mutex::Autolock lock(mStateLock);
-        const DisplayDevice* display = nullptr;
-        if (parent) {
-            const frontend::LayerSnapshot* snapshot =
-                    mLayerSnapshotBuilder.getSnapshot(parent->sequence);
-            if (snapshot) {
-                display = findDisplay([layerStack = snapshot->outputFilter.layerStack](
-                                              const auto& display) {
-                              return display.getLayerStack() == layerStack;
-                          }).get();
-            }
-        }
+    if (displayState) {
+        const auto& state = displayState.value();
+        captureResults.capturedDataspace =
+                pickBestDataspace(requestedDataspace, state, captureResults.capturedHdrLayers,
+                                  renderArea->getHintForSeamlessTransition());
+        sdrWhitePointNits = state.sdrWhitePointNits;
 
-        if (display == nullptr) {
-            display = renderArea->getDisplayDevice().get();
-        }
-
-        if (display == nullptr) {
-            display = getDefaultDisplayDeviceLocked().get();
-        }
-
-        if (display != nullptr) {
-            const auto& state = display->getCompositionDisplay()->getState();
-            captureResults.capturedDataspace =
-                    pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers,
-                                      renderArea->getHintForSeamlessTransition());
-            sdrWhitePointNits = state.sdrWhitePointNits;
-
-            if (!captureResults.capturedHdrLayers) {
-                displayBrightnessNits = sdrWhitePointNits;
-            } else {
-                displayBrightnessNits = state.displayBrightnessNits;
-
-                if (!enableLocalTonemapping) {
-                    // Only clamp the display brightness if this is not a seamless transition.
-                    // Otherwise for seamless transitions it's important to match the current
-                    // display state as the buffer will be shown under these same conditions, and we
-                    // want to avoid any flickers
-                    if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) {
-                        // Restrict the amount of HDR "headroom" in the screenshot to avoid
-                        // over-dimming the SDR portion. 2.0 chosen by experimentation
-                        constexpr float kMaxScreenshotHeadroom = 2.0f;
-                        displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom,
-                                                         displayBrightnessNits);
-                    }
+        if (!captureResults.capturedHdrLayers) {
+            displayBrightnessNits = sdrWhitePointNits;
+        } else {
+            displayBrightnessNits = state.displayBrightnessNits;
+            if (!enableLocalTonemapping) {
+                // Only clamp the display brightness if this is not a seamless transition.
+                // Otherwise for seamless transitions it's important to match the current
+                // display state as the buffer will be shown under these same conditions, and we
+                // want to avoid any flickers
+                if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) {
+                    // Restrict the amount of HDR "headroom" in the screenshot to avoid
+                    // over-dimming the SDR portion. 2.0 chosen by experimentation
+                    constexpr float kMaxScreenshotHeadroom = 2.0f;
+                    displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom,
+                                                     displayBrightnessNits);
                 }
             }
+        }
 
-            // Screenshots leaving the device should be colorimetric
-            if (requestedDataspace == ui::Dataspace::UNKNOWN &&
-                renderArea->getHintForSeamlessTransition()) {
-                renderIntent = state.renderIntent;
-            }
+        // Screenshots leaving the device should be colorimetric
+        if (requestedDataspace == ui::Dataspace::UNKNOWN &&
+            renderArea->getHintForSeamlessTransition()) {
+            renderIntent = state.renderIntent;
         }
     }
 
     captureResults.buffer = capturedBuffer->getBuffer();
 
     ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
-    if (!layers.empty()) {
-        const sp<LayerFE>& layerFE = layers.back().second;
+    if (!layerFEs.empty()) {
+        const sp<LayerFE>& layerFE = layerFEs.back();
         layerStack = layerFE->getCompositionState()->outputFilter.layerStack;
     }
 
-    auto copyLayerFEs = [&layers]() {
-        std::vector<sp<compositionengine::LayerFE>> layerFEs;
-        layerFEs.reserve(layers.size());
-        for (const auto& [_, layerFE] : layers) {
-            layerFEs.push_back(layerFE);
+    auto copyLayerFEs = [&layerFEs]() {
+        std::vector<sp<compositionengine::LayerFE>> ceLayerFEs;
+        ceLayerFEs.reserve(layerFEs.size());
+        for (const auto& layerFE : layerFEs) {
+            ceLayerFEs.push_back(layerFE);
         }
-        return layerFEs;
+        return ceLayerFEs;
     };
 
     auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
@@ -8474,8 +8557,16 @@
     //
     // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
     // to CompositionEngine::present.
-    auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
-                                                     : ftl::yield(present()).share();
+    ftl::SharedFuture<FenceResult> presentFuture;
+    if (FlagManager::getInstance().single_hop_screenshot() &&
+        FlagManager::getInstance().ce_fence_promise()) {
+        presentFuture = mRenderEngine->isThreaded()
+                ? ftl::yield(present()).share()
+                : mScheduler->schedule(std::move(present)).share();
+    } else {
+        presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
+                                                    : ftl::yield(present()).share();
+    }
 
     if (!FlagManager::getInstance().ce_fence_promise()) {
         for (auto& [layer, layerFE] : layers) {
@@ -8836,20 +8927,26 @@
 
 void SurfaceFlinger::enableRefreshRateOverlay(bool enable) {
     bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG);
-    for (const auto& [id, display] : mPhysicalDisplays) {
-        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal ||
+    for (const auto& [displayId, physical] : mPhysicalDisplays) {
+        if (physical.snapshot().connectionType() == ui::DisplayConnectionType::Internal ||
             FlagManager::getInstance().refresh_rate_overlay_on_external_display()) {
-            if (const auto device = getDisplayDeviceLocked(id)) {
-                const auto enableOverlay = [&](const bool setByHwc) FTL_FAKE_GUARD(
-                                                   kMainThreadContext) {
-                    device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner,
-                                                     mRefreshRateOverlayRenderRate,
-                                                     mRefreshRateOverlayShowInMiddle);
+            if (const auto display = getDisplayDeviceLocked(displayId)) {
+                const auto enableOverlay = [&](bool setByHwc) FTL_FAKE_GUARD(kMainThreadContext) {
+                    const auto activeMode = mDisplayModeController.getActiveMode(displayId);
+                    const Fps refreshRate = activeMode.modePtr->getVsyncRate();
+                    const Fps renderFps = activeMode.fps;
+
+                    display->enableRefreshRateOverlay(enable, setByHwc, refreshRate, renderFps,
+                                                      mRefreshRateOverlaySpinner,
+                                                      mRefreshRateOverlayRenderRate,
+                                                      mRefreshRateOverlayShowInMiddle);
                 };
+
                 enableOverlay(setByHwc);
                 if (setByHwc) {
                     const auto status =
-                            getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable);
+                            getHwComposer().setRefreshRateChangedCallbackDebugEnabled(displayId,
+                                                                                      enable);
                     if (status != NO_ERROR) {
                         ALOGE("Error %s refresh rate changed callback debug",
                               enable ? "enabling" : "disabling");
@@ -9005,7 +9102,7 @@
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    mScheduler->resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
+    mScheduler->resetPhaseConfiguration(mDisplayModeController.getActiveMode(mActiveDisplayId).fps);
 
     // TODO(b/255635711): Check for pending mode changes on other displays.
     mScheduler->setModeChangePending(false);
@@ -9239,7 +9336,9 @@
     std::vector<std::pair<Layer*, LayerFE*>> layers;
     if (mLayerLifecycleManagerEnabled) {
         nsecs_t currentTime = systemTime();
-        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+        const bool needsMetadata = mCompositionEngine->getFeatureFlags().test(
+                compositionengine::Feature::kSnapshotLayerMetadata);
+        mLayerSnapshotBuilder.forEachSnapshot(
                 [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
                         kMainThreadContext) {
                     if (cursorOnly &&
@@ -9262,6 +9361,12 @@
                     layerFE->mSnapshot = std::move(snapshot);
                     refreshArgs.layers.push_back(layerFE);
                     layers.emplace_back(legacyLayer.get(), layerFE.get());
+                },
+                [needsMetadata](const frontend::LayerSnapshot& snapshot) {
+                    return snapshot.isVisible ||
+                            (needsMetadata &&
+                             snapshot.changes.test(
+                                     frontend::RequestedLayerState::Changes::Metadata));
                 });
     }
     if (!mLayerLifecycleManagerEnabled) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 8a39016..7762bbe 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -559,7 +559,7 @@
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -737,12 +737,12 @@
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
 
-    void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
-    void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
+    void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) EXCLUDES(mStateLock);
+    void finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext)
+            EXCLUDES(mStateLock);
 
-    // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler.
-    void dropModeRequest(const sp<DisplayDevice>&) REQUIRES(mStateLock);
-    void applyActiveMode(const sp<DisplayDevice>&) REQUIRES(mStateLock);
+    void dropModeRequest(PhysicalDisplayId) REQUIRES(kMainThreadContext);
+    void applyActiveMode(PhysicalDisplayId) REQUIRES(kMainThreadContext);
 
     // Called on the main thread in response to setPowerMode()
     void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
@@ -892,32 +892,46 @@
     void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack);
 
     // Checks if a protected layer exists in a list of layers.
-    bool layersHasProtectedLayer(const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
+    bool layersHasProtectedLayer(const std::vector<sp<LayerFE>>& layers) const;
+
+    using OutputCompositionState = compositionengine::impl::OutputCompositionState;
+
+    std::optional<OutputCompositionState> getDisplayAndLayerSnapshotsFromMainThread(
+            RenderAreaBuilderVariant& renderAreaBuilder,
+            GetLayerSnapshotsFunction getLayerSnapshotsFn, std::vector<sp<LayerFE>>& layerFEs);
 
     void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
                              ui::Size bufferSize, ui::PixelFormat, bool allowProtected,
                              bool grayscale, const sp<IScreenCaptureListener>&);
 
+    std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
+            RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
+
+    // Legacy layer raw pointer is not safe to access outside the main thread.
+    // Creates a new vector consisting only of LayerFEs, which can be safely
+    // accessed outside the main thread.
+    std::vector<sp<LayerFE>> extractLayerFEs(
+            const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
+
     ftl::SharedFuture<FenceResult> captureScreenshot(
+            const RenderAreaBuilderVariant& renderAreaBuilder,
+            const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+            std::optional<OutputCompositionState>& displayState,
+            std::vector<sp<LayerFE>>& layerFEs);
+
+    ftl::SharedFuture<FenceResult> captureScreenshotLegacy(
             RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
 
-    // Overloaded version of renderScreenImpl that is used when layer snapshots have
-    // not yet been captured, and thus cannot yet be passed in as a parameter.
-    // Needed for TestableSurfaceFlinger.
-    ftl::SharedFuture<FenceResult> renderScreenImpl(
-            std::unique_ptr<const RenderArea>, GetLayerSnapshotsFunction,
-            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock)
-            REQUIRES(kMainThreadContext);
-
     ftl::SharedFuture<FenceResult> renderScreenImpl(
             std::unique_ptr<const RenderArea>,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, ScreenCaptureResults&,
-            std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) EXCLUDES(mStateLock)
-            REQUIRES(kMainThreadContext);
+            std::optional<OutputCompositionState>& displayState,
+            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
+            std::vector<sp<LayerFE>>& layerFEs);
 
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
     // matching ownerUid
@@ -1084,8 +1098,7 @@
                                const DisplayDeviceState& drawingState)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&)
-            REQUIRES(mStateLock);
+    void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&);
 
     /*
      * VSYNC
@@ -1325,9 +1338,7 @@
     display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock);
 
     // The inner or outer display for foldables, assuming they have mutually exclusive power states.
-    // Atomic because writes from onActiveDisplayChangedLocked are not always under mStateLock, but
-    // reads from ISchedulerCallback::requestDisplayModes may happen concurrently.
-    std::atomic<PhysicalDisplayId> mActiveDisplayId GUARDED_BY(mStateLock);
+    std::atomic<PhysicalDisplayId> mActiveDisplayId;
 
     display::DisplayModeController mDisplayModeController;
 
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index fc496b2..0bafb71 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -436,6 +436,7 @@
         layer.bufferData->flags = ftl::Flags<BufferData::BufferDataChange>(bufferProto.flags());
         layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id();
         layer.bufferData->acquireFence = Fence::NO_FENCE;
+        layer.bufferData->dequeueTime = -1;
     }
 
     if (proto.what() & layer_state_t::eApiChanged) {
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index 6b971a7..bcf1886 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -17,6 +17,7 @@
     shared_libs: [
         "libSurfaceFlingerProp",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "librenderengine_includes",
@@ -37,6 +38,7 @@
         "libsurfaceflingerflags",
         "android.os.flags-aconfig-cc",
         "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
     ],
 }
 
@@ -49,6 +51,7 @@
         "libsurfaceflingerflags_test",
         "android.os.flags-aconfig-cc-test",
         "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
     ],
 }
 
@@ -56,12 +59,14 @@
     name: "libsurfaceflinger_common_deps",
     shared_libs: [
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "libsurfaceflinger_common",
         "libsurfaceflingerflags",
         "android.os.flags-aconfig-cc",
         "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
     ],
 }
 
@@ -69,11 +74,13 @@
     name: "libsurfaceflinger_common_test_deps",
     shared_libs: [
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
         "android.os.flags-aconfig-cc-test",
         "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
     ],
 }
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 57b170f..a56bb51 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -27,6 +27,7 @@
 #include <cinttypes>
 
 #include <android_os.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include <com_android_server_display_feature_flags.h>
 
@@ -135,6 +136,7 @@
     DUMP_READ_ONLY_FLAG(vulkan_renderengine);
     DUMP_READ_ONLY_FLAG(renderable_buffer_usage);
     DUMP_READ_ONLY_FLAG(vrr_bugfix_24q4);
+    DUMP_READ_ONLY_FLAG(vrr_bugfix_dropped_frame);
     DUMP_READ_ONLY_FLAG(restore_blur_step);
     DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
     DUMP_READ_ONLY_FLAG(protected_if_client);
@@ -148,6 +150,10 @@
     DUMP_READ_ONLY_FLAG(commit_not_composited);
     DUMP_READ_ONLY_FLAG(local_tonemap_screenshots);
     DUMP_READ_ONLY_FLAG(override_trusted_overlay);
+    DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache);
+    DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine);
+    DUMP_READ_ONLY_FLAG(single_hop_screenshot);
+    DUMP_READ_ONLY_FLAG(trace_frame_rate_override);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -237,6 +243,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
 FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_24q4, "");
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_dropped_frame, "")
 FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
 FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
 FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
@@ -246,6 +253,9 @@
 FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, "");
 FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots");
 FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, "");
+FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, "");
+FLAG_MANAGER_READ_ONLY_FLAG(force_compile_graphite_renderengine, "");
+FLAG_MANAGER_READ_ONLY_FLAG(single_hop_screenshot, "");
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
@@ -258,5 +268,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "",
                                      com::android::server::display::feature::flags)
 FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os)
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(trace_frame_rate_override, "",
+                                     com::android::graphics::libgui::flags);
 
 } // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 9517ff7..8799295 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -73,6 +73,7 @@
     bool screenshot_fence_preservation() const;
     bool vulkan_renderengine() const;
     bool vrr_bugfix_24q4() const;
+    bool vrr_bugfix_dropped_frame() const;
     bool renderable_buffer_usage() const;
     bool restore_blur_step() const;
     bool dont_skip_on_early_ro() const;
@@ -87,6 +88,10 @@
     bool commit_not_composited() const;
     bool local_tonemap_screenshots() const;
     bool override_trusted_overlay() const;
+    bool flush_buffer_slots_to_uncache() const;
+    bool force_compile_graphite_renderengine() const;
+    bool single_hop_screenshot() const;
+    bool trace_frame_rate_override() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index dea74d0..56bca7f 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -161,7 +161,7 @@
 flag {
   name: "graphite_renderengine"
   namespace: "core_graphics"
-  description: "Use Skia's Graphite Vulkan backend in RenderEngine."
+  description: "Compile AND enable Skia's Graphite Vulkan backend in RenderEngine. See also: force_compile_graphite_renderengine."
   bug: "293371537"
   is_fixed_read_only: true
 }
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 02d8819..919ec17 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -55,6 +55,25 @@
 } # detached_mirror
 
 flag {
+  name: "flush_buffer_slots_to_uncache"
+  namespace: "core_graphics"
+  description: "Flush DisplayCommands for disabled displays in order to uncache requested buffers."
+  bug: "330806421"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # flush_buffer_slots_to_uncache
+
+flag {
+  name: "force_compile_graphite_renderengine"
+  namespace: "core_graphics"
+  description: "Compile Skia's Graphite Vulkan backend in RenderEngine, but do NOT enable it, unless graphite_renderengine is also set. It can also be enabled with the debug.renderengine.graphite system property for testing. In contrast, the graphite_renderengine flag both compiles AND enables Graphite in RenderEngine."
+  bug: "293371537"
+  is_fixed_read_only: true
+} # force_compile_graphite_renderengine
+
+flag {
   name: "frame_rate_category_mrr"
   namespace: "core_graphics"
   description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices"
@@ -84,6 +103,17 @@
     is_fixed_read_only: true
 } # local_tonemap_screenshots
 
+flag {
+  name: "single_hop_screenshot"
+  namespace: "window_surfaces"
+  description: "Only access SF main thread once during a screenshot"
+  bug: "285553970"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # single_hop_screenshot
+
  flag {
   name: "override_trusted_overlay"
   namespace: "window_surfaces"
@@ -106,4 +136,15 @@
   }
 } # vrr_bugfix_24q4
 
+flag {
+  name: "vrr_bugfix_dropped_frame"
+  namespace: "core_graphics"
+  description: "bug fix for VRR dropped frame"
+  bug: "343603085"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # vrr_bugfix_dropped_frame
+
 # IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index ebe11fb..d355e72 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -26,6 +26,7 @@
 #include <private/android_filesystem_config.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <utils/String8.h>
 #include <functional>
@@ -276,7 +277,7 @@
 TEST_F(CredentialsTest, CaptureLayersTest) {
     setupBackgroundSurface();
     sp<GraphicBuffer> outBuffer;
-    std::function<status_t()> condition = [=]() {
+    std::function<status_t()> condition = [=, this]() {
         LayerCaptureArgs captureArgs;
         captureArgs.layerHandle = mBGSurfaceControl->getHandle();
         captureArgs.sourceCrop = {0, 0, 1, 1};
@@ -396,6 +397,56 @@
     }
 }
 
+TEST_F(CredentialsTest, DisplayTransactionPermissionTest) {
+    const auto display = getFirstDisplayToken();
+
+    ui::DisplayState displayState;
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    const ui::Rotation initialOrientation = displayState.orientation;
+
+    // Set display orientation from an untrusted process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did not change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+
+    // Set display orientation from a trusted process.
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation + ui::ROTATION_90, displayState.orientation);
+
+    // Reset orientation
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation, layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5145e11..98d5754 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -70,9 +70,9 @@
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
         "DisplayDevice_GetBestColorModeTest.cpp",
-        "DisplayDevice_InitiateModeChange.cpp",
         "DisplayDevice_SetDisplayBrightnessTest.cpp",
         "DisplayDevice_SetProjectionTest.cpp",
+        "DisplayModeControllerTest.cpp",
         "EventThreadTest.cpp",
         "FlagManagerTest.cpp",
         "FpsReporterTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 0ddddbd..08973de 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -205,7 +205,8 @@
                                                    CaptureArgs::UNSET_UID, {}, visitor);
     };
 
-    auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    // TODO: Use SurfaceFlinger::getLayerSnapshotsForScreenshots instead of this legacy function
+    auto getLayerSnapshotsFn = RenderArea::fromTraverseLayersLambda(traverseLayers);
 
     const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
             GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
@@ -215,7 +216,7 @@
                                                                       HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                                       usage);
 
-    auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots,
+    auto future = mFlinger.renderScreenImpl(mDisplay, std::move(renderArea), getLayerSnapshotsFn,
                                             mCaptureScreenBuffer, regionSampling);
     ASSERT_TRUE(future.valid());
     const auto fenceResult = future.get();
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
deleted file mode 100644
index c463a92..0000000
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include "DisplayTransactionTestHelpers.h"
-#include "mock/MockFrameRateMode.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt)                               \
-    ASSERT_TRUE(requestOpt);                                                            \
-    EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \
-    EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent)
-
-namespace android {
-namespace {
-
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
-using DisplayModeRequest = display::DisplayModeRequest;
-
-class InitiateModeChangeTest : public DisplayTransactionTest {
-public:
-    using Action = DisplayDevice::DesiredModeAction;
-    void SetUp() override {
-        injectFakeBufferQueueFactory();
-        injectFakeNativeWindowSurfaceFactory();
-
-        PrimaryDisplayVariant::setupHwcHotplugCallExpectations(this);
-        PrimaryDisplayVariant::setupFramebufferConsumerBufferQueueCallExpectations(this);
-        PrimaryDisplayVariant::setupFramebufferProducerBufferQueueCallExpectations(this);
-        PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
-        PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
-
-        mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                           DisplayHotplugEvent::CONNECTED);
-        mFlinger.configureAndCommit();
-
-        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                           .setDisplayModes(makeModes(kMode60, kMode90, kMode120), kModeId60)
-                           .inject();
-    }
-
-protected:
-    sp<DisplayDevice> mDisplay;
-
-    static constexpr DisplayModeId kModeId60{0};
-    static constexpr DisplayModeId kModeId90{1};
-    static constexpr DisplayModeId kModeId120{2};
-
-    static inline const ftl::NonNull<DisplayModePtr> kMode60 =
-            ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
-    static inline const ftl::NonNull<DisplayModePtr> kMode90 =
-            ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
-    static inline const ftl::NonNull<DisplayModePtr> kMode120 =
-            ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
-
-    static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false};
-    static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true};
-    static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false};
-    static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true};
-};
-
-TEST_F(InitiateModeChangeTest, setDesiredModeToActiveMode) {
-    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode60)));
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, setDesiredMode) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
-
-    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, clearDesiredMode) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_TRUE(mDisplay->getDesiredMode());
-
-    mDisplay->clearDesiredMode();
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, initiateModeChange) REQUIRES(kMainThreadContext) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
-
-    const hal::VsyncPeriodChangeConstraints constraints{
-            .desiredTimeNanos = systemTime(),
-            .seamlessRequired = false,
-    };
-    hal::VsyncPeriodChangeTimeline timeline;
-    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
-
-    mDisplay->clearDesiredMode();
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, initiateRenderRateSwitch) {
-    EXPECT_EQ(Action::InitiateRenderRateSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode30)));
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
-
-    const hal::VsyncPeriodChangeConstraints constraints{
-            .desiredTimeNanos = systemTime(),
-            .seamlessRequired = false,
-    };
-    hal::VsyncPeriodChangeTimeline timeline;
-    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
-
-    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
-
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
-
-    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getPendingMode());
-
-    mDisplay->clearDesiredMode();
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
new file mode 100644
index 0000000..d971150
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "Display/DisplayModeController.h"
+#include "Display/DisplaySnapshot.h"
+#include "DisplayHardware/HWComposer.h"
+#include "DisplayIdentificationTestHelpers.h"
+#include "FpsOps.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockFrameRateMode.h"
+
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt)                               \
+    ASSERT_TRUE(requestOpt);                                                            \
+    EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \
+    EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent)
+
+namespace android::display {
+namespace {
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+class DisplayModeControllerTest : public testing::Test {
+public:
+    using Action = DisplayModeController::DesiredModeAction;
+
+    void SetUp() override {
+        mDmc.setHwComposer(mComposer.get());
+        mDmc.setActiveModeListener(
+                [this](PhysicalDisplayId displayId, Fps vsyncRate, Fps renderFps) {
+                    mActiveModeListener.Call(displayId, vsyncRate, renderFps);
+                });
+
+        constexpr uint8_t kPort = 111;
+        EXPECT_CALL(*mComposerHal, getDisplayIdentificationData(kHwcDisplayId, _, _))
+                .WillOnce(DoAll(SetArgPointee<1>(kPort), SetArgPointee<2>(getInternalEdid()),
+                                Return(hal::Error::NONE)));
+
+        EXPECT_CALL(*mComposerHal, setClientTargetSlotCount(kHwcDisplayId));
+        EXPECT_CALL(*mComposerHal,
+                    setVsyncEnabled(kHwcDisplayId, hal::IComposerClient::Vsync::DISABLE));
+        EXPECT_CALL(*mComposerHal, onHotplugConnect(kHwcDisplayId));
+
+        const auto infoOpt = mComposer->onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+        ASSERT_TRUE(infoOpt);
+
+        mDisplayId = infoOpt->id;
+        mDisplaySnapshotOpt.emplace(mDisplayId, ui::DisplayConnectionType::Internal,
+                                    makeModes(kMode60, kMode90, kMode120), ui::ColorModes{},
+                                    std::nullopt);
+
+        ftl::FakeGuard guard(kMainThreadContext);
+        mDmc.registerDisplay(*mDisplaySnapshotOpt, kModeId60,
+                             scheduler::RefreshRateSelector::Config{});
+    }
+
+protected:
+    hal::VsyncPeriodChangeConstraints expectModeSet(const DisplayModeRequest& request,
+                                                    hal::VsyncPeriodChangeTimeline& timeline,
+                                                    bool subsequent = false) {
+        EXPECT_CALL(*mComposerHal,
+                    isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching))
+                .WillOnce(Return(true));
+
+        if (!subsequent) {
+            EXPECT_CALL(*mComposerHal, getDisplayConnectionType(kHwcDisplayId, _))
+                    .WillOnce(DoAll(SetArgPointee<1>(
+                                            hal::IComposerClient::DisplayConnectionType::INTERNAL),
+                                    Return(hal::V2_4::Error::NONE)));
+        }
+
+        const hal::VsyncPeriodChangeConstraints constraints{
+                .desiredTimeNanos = systemTime(),
+                .seamlessRequired = false,
+        };
+
+        const hal::HWConfigId hwcModeId = request.mode.modePtr->getHwcId();
+
+        EXPECT_CALL(*mComposerHal,
+                    setActiveConfigWithConstraints(kHwcDisplayId, hwcModeId, constraints, _))
+                .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::V2_4::Error::NONE)));
+
+        return constraints;
+    }
+
+    static constexpr hal::HWDisplayId kHwcDisplayId = 1234;
+
+    Hwc2::mock::Composer* mComposerHal = new testing::StrictMock<Hwc2::mock::Composer>();
+    const std::unique_ptr<HWComposer> mComposer{
+            std::make_unique<impl::HWComposer>(std::unique_ptr<Hwc2::Composer>(mComposerHal))};
+
+    testing::MockFunction<void(PhysicalDisplayId, Fps, Fps)> mActiveModeListener;
+
+    DisplayModeController mDmc;
+
+    PhysicalDisplayId mDisplayId;
+    std::optional<DisplaySnapshot> mDisplaySnapshotOpt;
+
+    static constexpr DisplayModeId kModeId60{0};
+    static constexpr DisplayModeId kModeId90{1};
+    static constexpr DisplayModeId kModeId120{2};
+
+    static inline const ftl::NonNull<DisplayModePtr> kMode60 =
+            ftl::as_non_null(mock::createDisplayMode(kModeId60, 60_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode90 =
+            ftl::as_non_null(mock::createDisplayMode(kModeId90, 90_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode120 =
+            ftl::as_non_null(mock::createDisplayMode(kModeId120, 120_Hz));
+
+    static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false};
+    static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true};
+    static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false};
+    static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true};
+};
+
+TEST_F(DisplayModeControllerTest, setDesiredModeToActiveMode) {
+    EXPECT_CALL(mActiveModeListener, Call(_, _, _)).Times(0);
+
+    EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode60)));
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, setDesiredMode) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId));
+
+    // No action since a mode switch has already been initiated.
+    EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode120)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, clearDesiredMode) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+    EXPECT_TRUE(mDmc.getDesiredMode(mDisplayId));
+
+    mDmc.clearDesiredMode(mDisplayId);
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, initiateModeChange) REQUIRES(kMainThreadContext) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId));
+    auto modeRequest = kDesiredMode90;
+
+    hal::VsyncPeriodChangeTimeline timeline;
+    const auto constraints = expectModeSet(modeRequest, timeline);
+
+    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
+
+    mDmc.clearDesiredMode(mDisplayId);
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, initiateRenderRateSwitch) {
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 30_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateRenderRateSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode30)));
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId));
+    auto modeRequest = kDesiredMode90;
+
+    hal::VsyncPeriodChangeTimeline timeline;
+    auto constraints = expectModeSet(modeRequest, timeline);
+
+    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
+
+    // No action since a mode switch has already been initiated.
+    EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode120)));
+
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getDesiredMode(mDisplayId));
+    modeRequest = kDesiredMode120;
+
+    constexpr bool kSubsequent = true;
+    constraints = expectModeSet(modeRequest, timeline, kSubsequent);
+
+    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getPendingMode(mDisplayId));
+
+    mDmc.clearDesiredMode(mDisplayId);
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+} // namespace
+} // namespace android::display
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 2b333f4..b79bdb4 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -777,4 +777,28 @@
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
 }
 
+// (b/343901186)
+TEST_F(LayerHierarchyTest, cleanUpDanglingMirrorLayer) {
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14, 2, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    // destroy layer handle
+    reparentLayer(2, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 97bd79f..bc15dec 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -538,7 +538,8 @@
               ftl::Flags<RequestedLayerState::Changes>(
                       RequestedLayerState::Changes::Content |
                       RequestedLayerState::Changes::AffectsChildren |
-                      RequestedLayerState::Changes::VisibleRegion)
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::Input)
                       .string());
     EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(startingAlpha));
     mLifecycleManager.commitChanges();
@@ -551,7 +552,8 @@
               ftl::Flags<RequestedLayerState::Changes>(
                       RequestedLayerState::Changes::Content |
                       RequestedLayerState::Changes::AffectsChildren |
-                      RequestedLayerState::Changes::VisibleRegion)
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::Input)
                       .string());
     EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(endingAlpha));
     mLifecycleManager.commitChanges();
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index bd0accd..54d4659 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -213,6 +213,17 @@
     UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
 }
 
+TEST_F(LayerSnapshotTest, offscreenLayerSnapshotIsInvisible) {
+    EXPECT_EQ(getSnapshot(111)->isVisible, true);
+
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(11);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+    EXPECT_EQ(getSnapshot(111)->isVisible, false);
+    EXPECT_TRUE(getSnapshot(111)->changes.test(RequestedLayerState::Changes::Visibility));
+}
+
 // relative tests
 TEST_F(LayerSnapshotTest, RelativeParentCanHideChild) {
     reparentRelativeLayer(13, 11);
@@ -280,13 +291,106 @@
     transactions.back().states.front().layerId = 1;
     transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
     mLifecycleManager.applyTransactions(transactions);
-    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
     EXPECT_EQ(static_cast<int32_t>(getSnapshot(1)->gameMode), 42);
     EXPECT_EQ(static_cast<int32_t>(getSnapshot(11)->gameMode), 42);
 }
 
+TEST_F(LayerSnapshotTest, UpdateMetadata) {
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Metadata);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
+TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) {
+    hideLayer(1);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::Visibility |
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::AffectsChildren);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(static_cast<int64_t>(getSnapshot(1)->clientChanges),
+              layer_state_t::eMetadataChanged | layer_state_t::eFlagsChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
 TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) {
     // ROOT
     // ├── 1
@@ -1161,6 +1265,16 @@
             gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
 }
 
+TEST_F(LayerSnapshotTest, alphaChangesPropagateToInput) {
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    setAlpha(1, 0.5f);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->inputInfo.alpha, 0.5f);
+}
+
 TEST_F(LayerSnapshotTest, isFrontBuffered) {
     setBuffer(1,
               std::make_shared<renderengine::mock::FakeExternalTexture>(
@@ -1283,6 +1397,17 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) {
+    std::vector<uint32_t> visitedUniqueSequences;
+    mSnapshotBuilder.forEachSnapshot(
+            [&](const std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                visitedUniqueSequences.push_back(snapshot->uniqueSequence);
+            },
+            [](const frontend::LayerSnapshot& snapshot) { return snapshot.uniqueSequence == 111; });
+    EXPECT_EQ(visitedUniqueSequences.size(), 1u);
+    EXPECT_EQ(visitedUniqueSequences[0], 111u);
+}
+
 TEST_F(LayerSnapshotTest, canOccludePresentation) {
     setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation);
     LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index cf9a7d3..06c4e30 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1562,7 +1562,7 @@
             // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
-            {0_Hz, FrameRateCategory::Low, 30_Hz},
+            {0_Hz, FrameRateCategory::Low, 60_Hz},
             {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
@@ -1609,6 +1609,77 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_120_vrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    // Device with VRR config mode
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<Case> testCases = {
+            // Cases that only have frame rate category requirements, but no desired frame rate.
+            // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+            {0_Hz, FrameRateCategory::High, 120_Hz},
+            {0_Hz, FrameRateCategory::Normal, 60_Hz},
+            {0_Hz, FrameRateCategory::Low, 48_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 120_Hz},
+
+            // Cases that have both desired frame rate and frame rate category requirements.
+            {24_Hz, FrameRateCategory::High, 120_Hz},
+            {30_Hz, FrameRateCategory::High, 120_Hz},
+            {12_Hz, FrameRateCategory::Normal, 60_Hz},
+            {24_Hz, FrameRateCategory::Low, 48_Hz},
+            {30_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+            // Cases that only have desired frame rate.
+            {30_Hz, FrameRateCategory::Default, 30_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+              to_string(testCase.desiredFrameRate).c_str(),
+              ftl::enum_string(testCase.frameRateCategory).c_str());
+
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers).fps)
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
+    }
+}
+
 TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) {
     auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
@@ -2140,14 +2211,14 @@
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
             {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::Low, false, 60_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
 
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
             {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
@@ -2207,13 +2278,13 @@
             {FrameRateCategory::Default, false, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, false, 120_Hz},
-            {FrameRateCategory::Low, false, 30_Hz},
+            {FrameRateCategory::Low, false, 48_Hz},
             {FrameRateCategory::Normal, false, 60_Hz},
             {FrameRateCategory::High, false, 120_Hz},
             {FrameRateCategory::Default, true, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, true, 120_Hz},
-            {FrameRateCategory::Low, true, 30_Hz},
+            {FrameRateCategory::Low, true, 48_Hz},
             {FrameRateCategory::Normal, true, 60_Hz},
             {FrameRateCategory::High, true, 120_Hz},
     };
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 4fb0690..fc54a8b 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -343,12 +343,15 @@
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
     mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
@@ -411,10 +414,10 @@
     {
         // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120
         // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz.
-        mScheduler
-                ->registerDisplay(kDisplayId3,
-                                  std::make_shared<RefreshRateSelector>(kDisplay3Modes,
-                                                                        kDisplay3Mode60->getId()));
+        mScheduler->registerDisplay(kDisplayId3,
+                                    std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+                                                                          kDisplay3Mode60->getId()),
+                                    kActiveDisplayId);
         mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON);
 
         const GlobalSignals globalSignals = {.touch = true};
@@ -457,12 +460,15 @@
 }
 
 TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
 
@@ -585,7 +591,8 @@
                                 mFlinger.getTimeStats(),
                                 mSchedulerCallback};
 
-    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, std::nullopt,
+                              vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
     scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
     vrrTracker->addVsyncTimestamp(0);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 078b4fe..0c3e875 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -76,6 +76,7 @@
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
                            .setRefreshRateSelector(std::move(selectorPtr))
                            .inject(std::move(vsyncController), std::move(vsyncTracker));
+        mDisplayId = mDisplay->getPhysicalId();
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
         // will call setActiveConfig instead of setActiveConfigWithConstraints.
@@ -112,7 +113,11 @@
 protected:
     void setupScheduler(std::shared_ptr<scheduler::RefreshRateSelector>);
 
+    auto& dmc() { return mFlinger.mutableDisplayModeController(); }
+
     sp<DisplayDevice> mDisplay, mOuterDisplay;
+    PhysicalDisplayId mDisplayId;
+
     mock::EventThread* mAppEventThread;
 
     static constexpr DisplayModeId kModeId60{0};
@@ -167,17 +172,17 @@
 TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
 
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90);
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
@@ -187,8 +192,8 @@
 
     Mock::VerifyAndClearExpectations(mComposer);
 
-    EXPECT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_TRUE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     // Verify that the next commit will complete the mode change and send
     // a onModeChanged event to the framework.
@@ -198,23 +203,23 @@
     mFlinger.commit();
     Mock::VerifyAndClearExpectations(mAppEventThread);
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90);
 }
 
 TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90, true, 0, 120));
 
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90);
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
@@ -226,8 +231,8 @@
 
     mFlinger.commit();
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90);
 }
 
 TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
@@ -236,8 +241,8 @@
     // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
     // is still being processed the later call will be respected.
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
@@ -252,46 +257,45 @@
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId120, false, 0, 180));
 
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
+    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120);
 
     EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120);
 
     mFlinger.commit();
 
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
+    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120);
 
     mFlinger.commit();
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId120);
 }
 
 TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
     mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
                                         mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120));
 
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90_4K);
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90_4K);
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
     EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90_4K);
 
-    EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true));
+    EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplayId, true));
 
-    // Misc expecations. We don't need to enforce these method calls, but since the helper methods
-    // already set expectations we should add new ones here, otherwise the test will fail.
+    // Override expectations set up by PrimaryDisplayVariant.
     EXPECT_CALL(*mConsumer,
                 setDefaultBufferSize(static_cast<uint32_t>(kResolution4K.getWidth()),
                                      static_cast<uint32_t>(kResolution4K.getHeight())))
@@ -304,31 +308,28 @@
     injectFakeNativeWindowSurfaceFactory();
     PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
 
-    const auto displayToken = mDisplay->getDisplayToken().promote();
-
     mFlinger.commit();
 
-    // The DisplayDevice will be destroyed and recreated,
-    // so we need to update with the new instance.
-    mDisplay = mFlinger.getDisplay(displayToken);
-
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
+    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90_4K);
 }
 
 MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
-    if (!arg->getDesiredMode()) {
+    const auto displayId = arg->getPhysicalId();
+    auto& dmc = flinger->mutableDisplayModeController();
+
+    if (!dmc.getDesiredMode(displayId)) {
         *result_listener << "No desired mode";
         return false;
     }
 
-    if (arg->getDesiredMode()->mode.modePtr->getId() != modeId) {
+    if (dmc.getDesiredMode(displayId)->mode.modePtr->getId() != modeId) {
         *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId);
         return false;
     }
 
     // VsyncModulator should react to mode switches on the pacesetter display.
-    if (arg->getPhysicalId() == flinger->scheduler()->pacesetterDisplayId() &&
+    if (displayId == flinger->scheduler()->pacesetterDisplayId() &&
         !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
         *result_listener << "VsyncModulator did not shift to early phase";
         return false;
@@ -337,8 +338,10 @@
     return true;
 }
 
-MATCHER_P(ModeSettledTo, modeId, "") {
-    if (const auto desiredOpt = arg->getDesiredMode()) {
+MATCHER_P2(ModeSettledTo, dmc, modeId, "") {
+    const auto displayId = arg->getPhysicalId();
+
+    if (const auto desiredOpt = dmc->getDesiredMode(displayId)) {
         *result_listener << "Unsettled desired mode "
                          << ftl::to_underlying(desiredOpt->mode.modePtr->getId());
         return false;
@@ -346,7 +349,7 @@
 
     ftl::FakeGuard guard(kMainThreadContext);
 
-    if (arg->getActiveMode().modePtr->getId() != modeId) {
+    if (dmc->getActiveMode(displayId).modePtr->getId() != modeId) {
         *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId);
         return false;
     }
@@ -367,14 +370,14 @@
     EXPECT_TRUE(innerDisplay->isPoweredOn());
     EXPECT_FALSE(outerDisplay->isPoweredOn());
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF);
     mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
@@ -400,14 +403,14 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 
     mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF);
     mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
@@ -420,12 +423,12 @@
     mFlinger.commit();
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 }
 
 TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) {
@@ -440,14 +443,14 @@
     EXPECT_TRUE(innerDisplay->isPoweredOn());
     EXPECT_FALSE(outerDisplay->isPoweredOn());
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
     mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
@@ -473,13 +476,13 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) {
     EXPECT_TRUE(mDisplay->isPoweredOn());
-    EXPECT_THAT(mDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
@@ -502,7 +505,7 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(mDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90));
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
@@ -518,14 +521,14 @@
     EXPECT_TRUE(innerDisplay->isPoweredOn());
     EXPECT_FALSE(outerDisplay->isPoweredOn());
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
     mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
@@ -555,8 +558,8 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 
     mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF);
     mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
@@ -570,13 +573,13 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index 1bae5ff..933d03d 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -232,7 +232,9 @@
     // Invocation
 
     DisplayDeviceState state;
-    if constexpr (constexpr auto connectionType = Case::Display::CONNECTION_TYPE::value) {
+
+    constexpr auto kConnectionTypeOpt = Case::Display::CONNECTION_TYPE::value;
+    if constexpr (kConnectionTypeOpt) {
         const auto displayId = PhysicalDisplayId::tryCast(Case::Display::DISPLAY_ID::get());
         ASSERT_TRUE(displayId);
         const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
@@ -257,7 +259,7 @@
 
         const auto it = mFlinger.mutablePhysicalDisplays()
                                 .emplace_or_replace(*displayId, displayToken, *displayId,
-                                                    *connectionType, makeModes(activeMode),
+                                                    *kConnectionTypeOpt, makeModes(activeMode),
                                                     std::move(colorModes), std::nullopt)
                                 .first;
 
@@ -291,9 +293,12 @@
     EXPECT_EQ(Case::Display::DISPLAY_FLAGS & DisplayDevice::eReceivesInput,
               device->receivesInput());
 
-    if constexpr (Case::Display::CONNECTION_TYPE::value) {
+    if constexpr (kConnectionTypeOpt) {
         ftl::FakeGuard guard(kMainThreadContext);
-        EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().modePtr->getHwcId());
+        EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID,
+                  mFlinger.mutableDisplayModeController()
+                          .getActiveMode(device->getPhysicalId())
+                          .modePtr->getHwcId());
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 198a5de..f063809 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -53,7 +53,7 @@
                       factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
-                        std::move(tracker));
+                        std::move(tracker), displayId);
 
         ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
             // Execute task to prevent broken promise exception on destruction.
@@ -85,14 +85,16 @@
 
     void registerDisplay(
             PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+            std::optional<PhysicalDisplayId> activeDisplayIdOpt = {},
             std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
         registerDisplay(displayId, std::move(selectorPtr),
-                        std::make_unique<mock::VsyncController>(), vsyncTracker);
+                        std::make_unique<mock::VsyncController>(), vsyncTracker,
+                        activeDisplayIdOpt.value_or(displayId));
     }
 
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
                          std::unique_ptr<VsyncController> controller,
-                         std::shared_ptr<VSyncTracker> tracker) {
+                         std::shared_ptr<VSyncTracker> tracker, PhysicalDisplayId activeDisplayId) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr),
                                            std::shared_ptr<VsyncSchedule>(
@@ -101,16 +103,12 @@
                                                                              mock::VSyncDispatch>(),
                                                                      std::move(controller),
                                                                      mockRequestHardwareVsync
-                                                                             .AsStdFunction())));
+                                                                             .AsStdFunction())),
+                                           activeDisplayId);
     }
 
     testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;
 
-    void unregisterDisplay(PhysicalDisplayId displayId) {
-        ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::unregisterDisplay(displayId);
-    }
-
     void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::setDisplayPowerMode(displayId, powerMode);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 265f804..b5b36be 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -191,6 +191,8 @@
     void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
         mFlinger->mCompositionEngine->setHwComposer(
                 std::make_unique<impl::HWComposer>(std::move(composer)));
+        mFlinger->mDisplayModeController.setHwComposer(
+                &mFlinger->mCompositionEngine->getHwComposer());
     }
 
     void setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor> powerAdvisor) {
@@ -485,23 +487,28 @@
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto renderScreenImpl(std::unique_ptr<const RenderArea> renderArea,
-                          SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
+    auto renderScreenImpl(const sp<DisplayDevice> display,
+                          std::unique_ptr<const RenderArea> renderArea,
+                          SurfaceFlinger::GetLayerSnapshotsFunction getLayerSnapshotsFn,
                           const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                           bool regionSampling) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        ftl::FakeGuard guard(kMainThreadContext);
+
         ScreenCaptureResults captureResults;
-        return FTL_FAKE_GUARD(kMainThreadContext,
-                              mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers,
-                                                         buffer, regionSampling,
-                                                         false /* grayscale */,
-                                                         false /* isProtected */, captureResults));
+        auto displayState = std::optional{display->getCompositionDisplay()->getState()};
+        auto layers = getLayerSnapshotsFn();
+        auto layerFEs = mFlinger->extractLayerFEs(layers);
+
+        return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling,
+                                          false /* grayscale */, false /* isProtected */,
+                                          captureResults, displayState, layers, layerFEs);
     }
 
     auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
                                     std::unordered_set<uint32_t> excludeLayerIds,
                                     const LayerVector::Visitor& visitor) {
-        return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid,
-                                                                    excludeLayerIds, visitor);
+        return mFlinger->traverseLayersInLayerStack(layerStack, uid, excludeLayerIds, visitor);
     }
 
     auto getDisplayNativePrimaries(const sp<IBinder>& displayToken,
@@ -521,7 +528,7 @@
 
     auto setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -1050,7 +1057,6 @@
 
             auto& modes = mDisplayModes;
             auto& activeModeId = mActiveModeId;
-            std::optional<Fps> refreshRateOpt;
 
             DisplayDeviceState state;
             state.isSecure = mCreationArgs.isSecure;
@@ -1088,7 +1094,9 @@
 
                 const auto activeModeOpt = modes.get(activeModeId);
                 LOG_ALWAYS_FATAL_IF(!activeModeOpt);
-                refreshRateOpt = activeModeOpt->get()->getPeakFps();
+
+                // Save a copy for use after `modes` is consumed.
+                const Fps refreshRate = activeModeOpt->get()->getPeakFps();
 
                 state.physical = {.id = *physicalId,
                                   .hwcDisplayId = *mHwcDisplayId,
@@ -1104,21 +1112,20 @@
                         .registerDisplay(*physicalId, it->second.snapshot(),
                                          mCreationArgs.refreshRateSelector);
 
+                mFlinger.mutableDisplayModeController().setActiveMode(*physicalId, activeModeId,
+                                                                      refreshRate, refreshRate);
+
                 if (mFlinger.scheduler() && mSchedulerRegistration) {
                     mFlinger.scheduler()->registerDisplay(*physicalId,
                                                           mCreationArgs.refreshRateSelector,
-                                                          std::move(controller),
-                                                          std::move(tracker));
+                                                          std::move(controller), std::move(tracker),
+                                                          mFlinger.mutableActiveDisplayId());
                 }
             }
 
             sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
             mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
 
-            if (refreshRateOpt) {
-                display->setActiveMode(activeModeId, *refreshRateOpt, *refreshRateOpt);
-            }
-
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index d4d5b32..85b61f8 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -94,7 +94,7 @@
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
         layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false,
-                         dequeueTime, FrameTimelineInfo{});
+                         FrameTimelineInfo{});
 
         commitTransaction(layer.get());
         nsecs_t latchTime = 25;
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 5046a86..46733b9 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -99,7 +99,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo);
         acquireFence->signalForTest(12);
 
         commitTransaction(layer.get());
@@ -134,7 +134,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -151,7 +151,7 @@
                                                          2ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfo);
         nsecs_t end = systemTime();
         acquireFence2->signalForTest(12);
 
@@ -197,7 +197,7 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo);
         acquireFence->signalForTest(12);
 
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
@@ -232,7 +232,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
 
@@ -275,7 +275,7 @@
         FrameTimelineInfo ftInfo3;
         ftInfo3.vsyncId = 3;
         ftInfo3.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo3);
         EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -320,7 +320,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo);
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
@@ -335,7 +335,7 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfo);
         acquireFence2->signalForTest(12);
 
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -372,7 +372,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -392,7 +392,7 @@
         FrameTimelineInfo ftInfoInv;
         ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
         ftInfoInv.inputEventId = 0;
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfoInv);
         auto dropEndTime1 = systemTime();
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -413,7 +413,7 @@
         FrameTimelineInfo ftInfo2;
         ftInfo2.vsyncId = 2;
         ftInfo2.inputEventId = 0;
-        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2);
+        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, ftInfo2);
         auto dropEndTime2 = systemTime();
         acquireFence3->signalForTest(12);
 
@@ -462,7 +462,7 @@
             FrameTimelineInfo ftInfo;
             ftInfo.vsyncId = 1;
             ftInfo.inputEventId = 0;
-            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+            layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo);
             FrameTimelineInfo ftInfo2;
             ftInfo2.vsyncId = 2;
             ftInfo2.inputEventId = 0;
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index eafba0a..f36a8a6 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -535,6 +535,28 @@
     }
 }
 
+TEST_F(VSyncPredictorTest, isVSyncInPhaseWithRenderRate) {
+    SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true);
+    auto last = mNow;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + mPeriod), Eq(mNow + 2 * mPeriod));
+
+    const auto renderRateFps = Fps::fromPeriodNsecs(mPeriod * 2);
+    tracker.setRenderRate(renderRateFps, /*applyImmediately*/ true);
+
+    EXPECT_FALSE(tracker.isVSyncInPhase(mNow, renderRateFps));
+    EXPECT_TRUE(tracker.isVSyncInPhase(mNow + mPeriod, renderRateFps));
+    EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 2 * mPeriod, renderRateFps));
+    EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, renderRateFps));
+}
+
 TEST_F(VSyncPredictorTest, isVSyncInPhaseForDivisors) {
     auto last = mNow;
     for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
@@ -651,6 +673,36 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, setRenderRateWhenRenderRateGoesDown) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    Fps frameRate = Fps::fromPeriodNsecs(1000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    frameRate = Fps::fromPeriodNsecs(3000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    EXPECT_TRUE(vrrTracker.isVSyncInPhase(2000, frameRate));
+}
+
 TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
diff --git a/vulkan/Android.bp b/vulkan/Android.bp
index 2bf905c..0da9cc0 100644
--- a/vulkan/Android.bp
+++ b/vulkan/Android.bp
@@ -26,6 +26,8 @@
 
 cc_library_headers {
     name: "hwvulkan_headers",
+    host_supported: true, // Used for verification in Berberis.
+    native_bridge_supported: true, // Used for verification in Berberis.
     vendor_available: true,
     header_libs: [
         "libcutils_headers",
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index a9706bc..a3fe33e 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -178,7 +178,7 @@
     INIT_PROC(false, instance, GetPhysicalDeviceExternalSemaphoreProperties);
     INIT_PROC(false, instance, GetPhysicalDeviceExternalFenceProperties);
     INIT_PROC(false, instance, EnumeratePhysicalDeviceGroups);
-    INIT_PROC_EXT(KHR_swapchain, false, instance, GetPhysicalDevicePresentRectanglesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, instance, GetPhysicalDevicePresentRectanglesKHR);
     INIT_PROC(false, instance, GetPhysicalDeviceToolProperties);
     // clang-format on
 
@@ -325,9 +325,9 @@
     INIT_PROC(false, dev, BindBufferMemory2);
     INIT_PROC(false, dev, BindImageMemory2);
     INIT_PROC(false, dev, CmdSetDeviceMask);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupPresentCapabilitiesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupSurfacePresentModesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, AcquireNextImage2KHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupPresentCapabilitiesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupSurfacePresentModesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, AcquireNextImage2KHR);
     INIT_PROC(false, dev, CmdDispatchBase);
     INIT_PROC(false, dev, CreateDescriptorUpdateTemplate);
     INIT_PROC(false, dev, DestroyDescriptorUpdateTemplate);
@@ -659,6 +659,8 @@
         "vkGetDrmDisplayEXT",
         "vkGetInstanceProcAddr",
         "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT",
+        "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR",
+        "vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR",
         "vkGetPhysicalDeviceDisplayPlaneProperties2KHR",
         "vkGetPhysicalDeviceDisplayProperties2KHR",
         "vkGetPhysicalDeviceExternalBufferProperties",
@@ -703,6 +705,7 @@
         "vkGetPhysicalDeviceToolProperties",
         "vkGetPhysicalDeviceToolPropertiesEXT",
         "vkGetPhysicalDeviceVideoCapabilitiesKHR",
+        "vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR",
         "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
         "vkSubmitDebugUtilsMessageEXT",
     };
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 74d3d9d..00e987f 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1174,7 +1174,8 @@
                             pSurfaceFormat);
 
                     if (surfaceCompressionProps &&
-                        driver.GetPhysicalDeviceImageFormatProperties2KHR) {
+                        (driver.GetPhysicalDeviceImageFormatProperties2KHR ||
+                         driver.GetPhysicalDeviceImageFormatProperties2)) {
                         VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {};
                         imageFormatInfo.sType =
                             VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
@@ -1205,7 +1206,7 @@
                         imageFormatProps.pNext = &compressionProps;
 
                         VkResult compressionRes =
-                            driver.GetPhysicalDeviceImageFormatProperties2KHR(
+                            GetPhysicalDeviceImageFormatProperties2(
                                 physicalDevice, &imageFormatInfo,
                                 &imageFormatProps);
                         if (compressionRes == VK_SUCCESS) {
@@ -1472,23 +1473,14 @@
         image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
         image_format_properties.pNext = &ahb_usage;
 
-        if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
-                pdev, &image_format_info, &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-        }
-        else {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
-                pdev, &image_format_info,
-                &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
-                    result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
+        VkResult result = GetPhysicalDeviceImageFormatProperties2(
+            pdev, &image_format_info, &image_format_properties);
+        if (result != VK_SUCCESS) {
+            ALOGE(
+                "VkGetPhysicalDeviceImageFormatProperties2 for AHB usage "
+                "failed: %d",
+                result);
+            return VK_ERROR_SURFACE_LOST_KHR;
         }
 
         // Determine if USAGE_FRONT_BUFFER is needed.
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 866c1b7..6b4cbad 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -351,9 +351,50 @@
                           'external', 'vulkan-headers', 'registry', 'vk.xml')
   tree = element_tree.parse(registry)
   root = tree.getroot()
+
+  for exts in root.iter('extensions'):
+    for extension in exts.iter('extension'):
+      if 'vulkan' not in extension.get('supported').split(','):
+        # ANDROID_native_buffer is a weird special case -- it's declared in vk.xml as
+        # disabled but we _do_ want to generate plumbing for it in the Android loader.
+        if extension.get('name') != 'VK_ANDROID_native_buffer':
+          print('skip extension disabled or not for vulkan: ' + extension.get('name'))
+          continue
+
+      apiversion = 'VK_VERSION_1_0'
+      if extension.tag == 'extension':
+        extname = extension.get('name')
+        if (extension.get('type') == 'instance' and
+            extension.get('promotedto') is not None):
+          promoted_inst_ext_dict[extname] = \
+              version_2_api_version(extension.get('promotedto'))
+        for req in extension.iter('require'):
+          if req.get('feature') is not None:
+            apiversion = req.get('feature')
+          for commands in req:
+            if commands.tag == 'command':
+              cmd_name = commands.get('name')
+              if cmd_name not in extension_dict:
+                extension_dict[cmd_name] = extname
+                version_dict[cmd_name] = apiversion
+
+  for feature in root.iter('feature'):
+    if 'vulkan' not in feature.get('api').split(','):
+      continue
+
+    apiversion = feature.get('name')
+    for req in feature.iter('require'):
+      for command in req:
+        if command.tag == 'command':
+          cmd_name = command.get('name')
+          version_dict[cmd_name] = apiversion
+
   for commands in root.iter('commands'):
     for command in commands:
       if command.tag == 'command':
+        if command.get('api') == 'vulkansc':
+          continue
+
         parameter_list = []
         protoset = False
         cmd_name = ''
@@ -361,12 +402,18 @@
         if command.get('alias') is not None:
           alias = command.get('alias')
           cmd_name = command.get('name')
-          alias_dict[cmd_name] = alias
-          command_list.append(cmd_name)
-          param_dict[cmd_name] = param_dict[alias].copy()
-          return_type_dict[cmd_name] = return_type_dict[alias]
+          # At this stage all valid commands have been added to the version
+          # dict so we can use it to filter valid commands
+          if cmd_name in version_dict:
+            alias_dict[cmd_name] = alias
+            command_list.append(cmd_name)
+            param_dict[cmd_name] = param_dict[alias].copy()
+            return_type_dict[cmd_name] = return_type_dict[alias]
         for params in command:
           if params.tag == 'param':
+            if params.get('api') == 'vulkansc':
+              # skip SC-only param variant
+              continue
             param_type = ''
             if params.text is not None and params.text.strip():
               param_type = params.text.strip() + ' '
@@ -387,39 +434,13 @@
                 cmd_type = c.text
               if c.tag == 'name':
                 cmd_name = c.text
-                protoset = True
-                command_list.append(cmd_name)
-                return_type_dict[cmd_name] = cmd_type
+                if cmd_name in version_dict:
+                  protoset = True
+                  command_list.append(cmd_name)
+                  return_type_dict[cmd_name] = cmd_type
         if protoset:
           param_dict[cmd_name] = parameter_list.copy()
 
-  for exts in root.iter('extensions'):
-    for extension in exts:
-      apiversion = 'VK_VERSION_1_0'
-      if extension.tag == 'extension':
-        extname = extension.get('name')
-        if (extension.get('type') == 'instance' and
-            extension.get('promotedto') is not None):
-          promoted_inst_ext_dict[extname] = \
-              version_2_api_version(extension.get('promotedto'))
-        for req in extension:
-          if req.get('feature') is not None:
-            apiversion = req.get('feature')
-          for commands in req:
-            if commands.tag == 'command':
-              cmd_name = commands.get('name')
-              if cmd_name not in extension_dict:
-                extension_dict[cmd_name] = extname
-                version_dict[cmd_name] = apiversion
-
-  for feature in root.iter('feature'):
-    apiversion = feature.get('name')
-    for req in feature:
-      for command in req:
-        if command.tag == 'command':
-          cmd_name = command.get('name')
-          if cmd_name in command_list:
-            version_dict[cmd_name] = apiversion
 
   version_code_set = set()
   for version in version_dict.values():
diff --git a/vulkan/scripts/null_generator.py b/vulkan/scripts/null_generator.py
index e9faef6..3624c1d 100644
--- a/vulkan/scripts/null_generator.py
+++ b/vulkan/scripts/null_generator.py
@@ -89,6 +89,8 @@
     f.write(gencom.copyright_and_warning(2015))
 
     f.write("""\
+#include <android/hardware_buffer.h>
+
 #include <algorithm>
 
 #include "null_driver_gen.h"