Merge "MotionEvent: Consolidate functions to transform PointerCoords" into main
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/input/Input.h b/include/input/Input.h
index 3e7a6fd..ec08cdd 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -274,6 +274,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;
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/binder/Android.bp b/libs/binder/Android.bp
index 090e35b..cb1d114 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -28,6 +28,7 @@
     recovery_available: true,
     host_supported: true,
     native_bridge_supported: true,
+    cmake_snapshot_supported: true,
 
     header_libs: [
         "libbinder_headers_platform_shared",
@@ -83,6 +84,124 @@
     },
 }
 
+cc_cmake_snapshot {
+    name: "binder_sdk",
+    modules: [
+        "libbinder_sdk",
+        "libbinder_sdk_single_threaded",
+        "libbinder_ndk_sdk",
+        "binderRpcTestNoKernel",
+    ],
+    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_system: "GTest",
+        },
+        {
+            android_name: "libgtest_main",
+            mapped_name: "GTest::gtest",
+            package_system: "GTest",
+        },
+
+        // 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 +245,9 @@
     header_libs: [
         "libbinder_headers_base",
     ],
+    export_header_lib_headers: [
+        "libbinder_headers_base",
+    ],
 
     cflags: [
         "-Wextra",
@@ -369,6 +491,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 +531,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 +544,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 +576,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 +678,7 @@
     defaults: ["libbinder_tls_shared_deps"],
     vendor_available: true,
     host_supported: true,
+    cmake_snapshot_supported: true,
 
     header_libs: [
         "libbinder_headers",
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_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/tests/Android.bp b/libs/binder/tests/Android.bp
index 8771af5..3fe55d6 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",
@@ -142,6 +143,7 @@
     name: "binderRpcTestIface",
     vendor_available: true,
     host_supported: true,
+    cmake_snapshot_supported: true,
     unstable: true,
     srcs: [
         "BinderRpcTestClientInfo.aidl",
@@ -223,6 +225,7 @@
 cc_defaults {
     name: "binderRpcTest_common_defaults",
     host_supported: true,
+    cmake_snapshot_supported: true,
     target: {
         darwin: {
             enabled: false,
@@ -382,6 +385,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded_no_kernel",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_binary {
@@ -502,6 +508,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded_no_kernel",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_test {
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index ed17014..d782f42 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -113,6 +113,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 +225,7 @@
         "InputVerifier.cpp",
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
+        "KeyboardClassifier.cpp",
         "KeyLayoutMap.cpp",
         "MotionPredictor.cpp",
         "MotionPredictorMetricsManager.cpp",
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 650dc5c..a77dfa5 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -259,4 +259,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/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..0574245 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -16,22 +16,26 @@
 
 //! 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 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,
@@ -192,23 +196,23 @@
     #[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 = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED 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 = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
         /// FLAG_HOVER_EXIT_PENDING
-        const HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        const HOVER_EXIT_PENDING = IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
         /// FLAG_IS_GENERATED_GESTURE
-        const IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        const IS_GENERATED_GESTURE = IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
         /// FLAG_CANCELED
-        const CANCELED = INPUT_EVENT_FLAG_CANCELED as u32;
+        const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
         /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
         /// FLAG_TAINTED
-        const TAINTED = INPUT_EVENT_FLAG_TAINTED as u32;
+        const TAINTED = IInputConstants::INPUT_EVENT_FLAG_TAINTED as u32;
         /// FLAG_TARGET_ACCESSIBILITY_FOCUS
-        const TARGET_ACCESSIBILITY_FOCUS = MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
+        const TARGET_ACCESSIBILITY_FOCUS = IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
     }
 }
 
@@ -220,6 +224,107 @@
     }
 }
 
+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;
diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs
new file mode 100644
index 0000000..1063fac
--- /dev/null
+++ b/libs/input/rust/keyboard_classifier.rs
@@ -0,0 +1,345 @@
+/*
+ * 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::{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)
+            };
+        }
+        // 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_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));
+    }
+
+    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,
+        }
+    }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index fb3f520..5010475 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -18,9 +18,13 @@
 
 mod input;
 mod input_verifier;
+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 +51,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 +64,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 +146,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/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index 06d5439..0783714 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -65,5 +65,6 @@
         "libui",
         "libutils",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
 }
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 96d9dc1..5ed5eb8 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1896,8 +1896,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;
@@ -1914,8 +1912,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;
     }
 
@@ -3315,22 +3317,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: {
@@ -5531,6 +5527,10 @@
             }
             mFocusedDisplayId = displayId;
 
+            // Only a window on the focused display can have Pointer Capture, so disable the active
+            // Pointer Capture session if there is one, since the focused display changed.
+            disablePointerCaptureForcedLocked();
+
             // Find new focused window and validate
             sp<IBinder> newFocusedWindowToken = mFocusResolver.getFocusedWindowToken(displayId);
             sendFocusChangedCommandLocked(oldFocusedWindowToken, newFocusedWindowToken);
@@ -6931,17 +6931,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/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 9b9633a..3d30ad6 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,9 @@
         }
         const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
-        auto* inputEvent = tracePacket->set_android_input_event();
+        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 +257,9 @@
         }
         const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
-        auto* inputEvent = tracePacket->set_android_input_event();
+        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 +283,9 @@
         }
         const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
-        auto* inputEvent = tracePacket->set_android_input_event();
+        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/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index ed05bdd..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()) {
@@ -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/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 39d2f5b..7cf584d 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -85,64 +85,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 {
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 4c9af2e..2a7e262 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;
@@ -470,6 +475,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/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 1bc87a2..91ec62d 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);
@@ -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;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 74bef46..c7df558 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -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/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/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index 530416c..e17ee3a 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -215,6 +215,10 @@
     mStaleEventTimeout = timeout;
 }
 
+void FakeInputDispatcherPolicy::setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching) {
+    mConsumeKeyBeforeDispatching = consumeKeyBeforeDispatching;
+}
+
 void FakeInputDispatcherPolicy::assertUserActivityNotPoked() {
     std::unique_lock lock(mLock);
     base::ScopedLockAssertion assumeLocked(mLock);
@@ -401,6 +405,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;
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index 2c86146..62ff10f 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -115,6 +115,7 @@
     void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler);
     void assertUnhandledKeyReported(int32_t keycode);
     void assertUnhandledKeyNotReported();
+    void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching);
 
 private:
     std::mutex mLock;
@@ -144,6 +145,8 @@
 
     std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
 
+    bool mConsumeKeyBeforeDispatching = false;
+
     BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
 
     std::condition_variable mNotifyUnhandledKey;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 8de28c6..56a05a3 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) {
@@ -6628,17 +6606,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 +6629,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 +6655,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 +6668,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 =
@@ -11054,6 +11047,37 @@
     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);
+
+    // 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,
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 804b4f7..fe238f3 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -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;
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
index 32acb5f..a9d370a 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,45 @@
 
     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;
+            }
+
+            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 6a35631..4441724 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -38,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>
@@ -77,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 {
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 031b77d..ada841d 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -67,8 +67,7 @@
         EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get()));
 
         mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
-                                                         AINPUT_SOURCE_KEYBOARD,
-                                                         AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                         AINPUT_SOURCE_KEYBOARD);
     }
 
     void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index 8c3189e..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) {
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 25f2f2e..ff425dd 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -338,9 +338,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/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 38cf053..a57e626 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -474,7 +474,6 @@
         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) {
@@ -489,6 +488,9 @@
     ATRACE_CALL();
     if (mRefreshRateOverlay) {
         if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
+            if (mRefreshRateSelector->isVrrDevice() && !mRefreshRateOverlay->isSetByHwc()) {
+                refreshRate = renderFps;
+            }
             mRefreshRateOverlay->changeRefreshRate(refreshRate, renderFps);
         } else {
             mRefreshRateOverlay->changeRenderRate(renderFps);
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 59eb7f5..5add290 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -348,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),
-                                           getLayerSnapshotsFn, 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/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 85ce713..dd3c4b0 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -737,7 +737,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/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 97469c0..59345db 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -8127,12 +8127,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;
         }
@@ -8140,6 +8143,26 @@
     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 getLayerSnapshotsFn,
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
@@ -8155,47 +8178,85 @@
         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 getLayerSnapshotsFn(); }).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, getLayerSnapshotsFn, texture,
-                              false /* regionSampling */, grayscale, isProtected, captureListener);
-    futureFence.get();
 }
 
-const sp<const DisplayDevice> SurfaceFlinger::getRenderAreaDisplay(
-        RenderAreaBuilderVariant& renderAreaBuilder, OutputCompositionState& state) {
+std::optional<SurfaceFlinger::OutputCompositionState>
+SurfaceFlinger::getDisplayStateFromRenderAreaBuilder(RenderAreaBuilderVariant& renderAreaBuilder) {
     sp<const DisplayDevice> display = nullptr;
     {
         Mutex::Autolock lock(mStateLock);
@@ -8224,24 +8285,64 @@
         }
 
         if (display != nullptr) {
-            state = display->getCompositionDisplay()->getState();
+            return std::optional{display->getCompositionDisplay()->getState()};
         }
     }
-    return display;
+    return std::nullopt;
 }
 
-std::vector<std::pair<Layer*, sp<android::LayerFE>>>
-SurfaceFlinger::getLayerSnapshotsFromMainThread(GetLayerSnapshotsFunction getLayerSnapshotsFn) {
-    auto layers = getLayerSnapshotsFn();
-    if (FlagManager::getInstance().ce_fence_promise()) {
-        for (auto& [layer, layerFE] : layers) {
-            attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
-        }
+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 layers;
+    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) {
@@ -8249,10 +8350,13 @@
 
     auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
                                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
-        auto layers = getLayerSnapshotsFromMainThread(getLayerSnapshotsFn);
-
-        OutputCompositionState state;
-        const auto display = getRenderAreaDisplay(renderAreaBuilder, state);
+        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 =
@@ -8268,9 +8372,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, display, state, layers);
+                                 isProtected, captureResults, displayState, layers, layerFEs);
 
         if (captureListener) {
             // Defer blocking on renderFuture back to the Binder thread.
@@ -8303,11 +8408,11 @@
         std::unique_ptr<const RenderArea> renderArea,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
-        const sp<const DisplayDevice> display, const OutputCompositionState& state,
-        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);
@@ -8330,7 +8435,8 @@
     const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() &&
             !renderArea->getHintForSeamlessTransition();
 
-    if (display != nullptr) {
+    if (displayState) {
+        const auto& state = displayState.value();
         captureResults.capturedDataspace =
                 pickBestDataspace(requestedDataspace, state, captureResults.capturedHdrLayers,
                                   renderArea->getHintForSeamlessTransition());
@@ -8365,18 +8471,18 @@
     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,
@@ -8445,8 +8551,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) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 209d9bc..1230717 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -892,22 +892,35 @@
     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>&);
 
-    using OutputCompositionState = compositionengine::impl::OutputCompositionState;
+    std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
+            RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
 
-    const sp<const DisplayDevice> getRenderAreaDisplay(RenderAreaBuilderVariant& renderAreaBuilder,
-                                                       OutputCompositionState& state)
-            REQUIRES(kMainThreadContext);
-
-    std::vector<std::pair<Layer*, sp<android::LayerFE>>> getLayerSnapshotsFromMainThread(
-            GetLayerSnapshotsFunction getLayerSnapshotsFn) 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>&);
@@ -916,9 +929,9 @@
             std::unique_ptr<const RenderArea>,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, ScreenCaptureResults&,
-            const sp<const DisplayDevice> display, const OutputCompositionState& state,
-            std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers)
-            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
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index 6b971a7..c3594bc 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",
@@ -56,6 +57,7 @@
     name: "libsurfaceflinger_common_deps",
     shared_libs: [
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "libsurfaceflinger_common",
@@ -69,6 +71,7 @@
     name: "libsurfaceflinger_common_test_deps",
     shared_libs: [
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "libsurfaceflinger_common_test",
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index b7ec6e0..4216771 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -150,6 +150,7 @@
     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);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -250,6 +251,7 @@
 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, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 8f98ed3..22118ab 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -89,6 +89,7 @@
     bool override_trusted_overlay() const;
     bool flush_buffer_slots_to_uncache() const;
     bool force_compile_graphite_renderengine() const;
+    bool single_hop_screenshot() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index ee12325..f4d4ee9 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -103,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"
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 7889096..8c72a7d 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -494,12 +494,13 @@
         ftl::FakeGuard guard(kMainThreadContext);
 
         ScreenCaptureResults captureResults;
-        SurfaceFlinger::OutputCompositionState state = display->getCompositionDisplay()->getState();
-        auto layers = mFlinger->getLayerSnapshotsFromMainThread(getLayerSnapshotsFn);
+        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, display, state, layers);
+                                          captureResults, displayState, layers, layerFEs);
     }
 
     auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index eafba0a..5109ea6 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++) {