Merge "[Lut HAL backend] skip libtonemap if the lut(s) is in use" into main
diff --git a/Android.bp b/Android.bp
index 13954ef..a689b2d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -48,6 +48,7 @@
     export_include_dirs: [
         "include/",
     ],
+    product_available: true,
 }
 
 ndk_headers {
diff --git a/data/etc/android.hardware.bluetooth_le.channel_sounding.xml b/data/etc/android.hardware.bluetooth_le.channel_sounding.xml
new file mode 100644
index 0000000..f0ee5d9
--- /dev/null
+++ b/data/etc/android.hardware.bluetooth_le.channel_sounding.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Adds the feature indicating support for the BLE Channel sounding -->
+<permissions>
+    <feature name="android.hardware.bluetooth_le.channel_sounding" />
+</permissions>
diff --git a/include/android/OWNERS b/include/android/OWNERS
index fad8c1b..5b014d0 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -3,3 +3,10 @@
 # Window manager
 per-file surface_control_input_receiver.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
 per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+
+# CoGS
+per-file *luts* = file:platform/frameworks/base:/graphics/java/android/graphics/OWNERS
+
+# ADPF
+per-file performance_hint.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file thermal.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 3486e9b..37d320a 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -19,9 +19,23 @@
  *
  * APerformanceHint allows apps to create performance hint sessions for groups
  * of threads, and provide hints to the system about the workload of those threads,
- * to help the system more accurately allocate power for them. It is the NDK
+ * to help the system more accurately allocate resources for them. It is the NDK
  * counterpart to the Java PerformanceHintManager SDK API.
  *
+ * This API is intended for periodic workloads, such as frame production. Clients are
+ * expected to create an instance of APerformanceHintManager, create a session with
+ * that, and then set a target duration for the session. Then, they can report the actual
+ * work duration at the end of each cycle to inform the framework about how long those
+ * workloads are taking. The framework will then compare the actual durations to the target
+ * duration and attempt to help the client reach a steady state under the target.
+ *
+ * Unlike reportActualWorkDuration, the "notify..." hints are intended to be sent in
+ * advance of large changes in the workload, to prevent them from going over the target
+ * when there is a sudden, unforseen change. Their effects are intended to last for only
+ * one cycle, after which reportActualWorkDuration will have a chance to catch up.
+ * These hints should be used judiciously, only in cases where the workload is changing
+ * substantially. To enforce that, they are tracked using a per-app rate limiter to avoid
+ * excessive hinting and encourage clients to be mindful about when to send them.
  * @{
  */
 
@@ -35,6 +49,7 @@
 #define ANDROID_NATIVE_PERFORMANCE_HINT_H
 
 #include <sys/cdefs.h>
+#include <jni.h>
 
 /******************************************************************
  *
@@ -96,9 +111,24 @@
 typedef struct APerformanceHintManager APerformanceHintManager;
 
 /**
+ * An opaque type representing a handle to a performance hint session creation configuration.
+ * It is consumed by {@link APerformanceHint_createSessionUsingConfig}.
+ *
+ * A session creation config encapsulates the required information for a session.
+ * Additionally, the caller can set various settings for the session,
+ * to be passed during creation, streamlining the session setup process.
+ *
+ * The caller may reuse this object and modify the settings in it
+ * to create additional sessions.
+ *
+ */
+typedef struct ASessionCreationConfig ASessionCreationConfig;
+
+/**
  * An opaque type representing a handle to a performance hint session.
  * A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_createSession}. It must be
+ * with {@link APerformanceHint_createSession}
+ * or {@link APerformanceHint_createSessionUsingConfig}. It must be
  * freed with {@link APerformanceHint_closeSession} after use.
  *
  * A Session represents a group of threads with an inter-related workload such that hints for
@@ -138,7 +168,7 @@
  * @param size The size of the list of threadIds.
  * @param initialTargetWorkDurationNanos The target duration in nanoseconds for the new session.
  *     This must be positive if using the work duration API, or 0 otherwise.
- * @return APerformanceHintManager instance on success, nullptr on failure.
+ * @return APerformanceHintSession pointer on success, nullptr on failure.
  */
 APerformanceHintSession* _Nullable APerformanceHint_createSession(
         APerformanceHintManager* _Nonnull manager,
@@ -146,6 +176,20 @@
         int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
+ * Creates a session for the given set of threads that are graphics pipeline threads
+ * and set their initial target work duration.
+ *
+ * @param manager The performance hint manager instance.
+ * @param config The configuration struct containing required information
+ *        to create a session.
+ * @return APerformanceHintSession pointer on success, nullptr on failure.
+ */
+APerformanceHintSession* _Nullable APerformanceHint_createSessionUsingConfig(
+        APerformanceHintManager* _Nonnull manager,
+        ASessionCreationConfig* _Nonnull config)
+        __INTRODUCED_IN(36);
+
+/**
  * Get preferred update rate information for this device.
  *
  * @param manager The performance hint manager instance.
@@ -155,6 +199,15 @@
         APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
+ * Get maximum number of graphics pipieline threads per-app for this device.
+ *
+ * @param manager The performance hint manager instance.
+ * @return the maximum number of graphics pipeline threads supported by device.
+ */
+ int APerformanceHint_getMaxGraphicsPipelineThreadsCount(
+        APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(36);
+
+/**
  * Updates this session's target duration for each cycle of work.
  *
  * @param session The performance hint session instance to update.
@@ -188,6 +241,9 @@
  * Release the performance hint manager pointer acquired via
  * {@link APerformanceHint_createSession}.
  *
+ * This cannot be used to close a Java PerformanceHintManager.Session, as its
+ * lifecycle is tied to the object in the SDK.
+ *
  * @param session The performance hint session instance to release.
  */
 void APerformanceHint_closeSession(
@@ -250,16 +306,64 @@
         AWorkDuration* _Nonnull workDuration) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
+ * Informs the framework of an upcoming increase in the workload of a graphics pipeline
+ * bound to this session. The user can specify whether the increase is expected to be
+ * on the CPU, GPU, or both.
+ *
+ * Sending hints for both CPU and GPU counts as two separate hints for the purposes of the
+ * rate limiter.
+ *
+ * @param cpu Indicates if the workload increase is expected to affect the CPU.
+ * @param gpu Indicates if the workload increase is expected to affect the GPU.
+ * @param debugName A required string used to identify this specific hint during
+ *        tracing. This debug string will only be held for the duration of the
+ *        method, and can be safely discarded after.
+ *
+ * @return 0 on success.
+ *         EINVAL if no hints were requested.
+ *         EBUSY if the hint was rate limited.
+ *         EPIPE if communication with the system service has failed.
+ *         ENOTSUP if the hint is not supported.
+ */
+int APerformanceHint_notifyWorkloadIncrease(
+        APerformanceHintSession* _Nonnull session,
+        bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
+
+/**
+ * Informs the framework of an upcoming reset in the workload of a graphics pipeline
+ * bound to this session, or the imminent start of a new workload. The user can specify
+ * whether the reset is expected to affect the CPU, GPU, or both.
+ *
+ * Sending hints for both CPU and GPU counts as two separate hints for the purposes of the
+ * this load tracking.
+ *
+ * @param cpu Indicates if the workload reset is expected to affect the CPU.
+ * @param gpu Indicates if the workload reset is expected to affect the GPU.
+ * @param debugName A required string used to identify this specific hint during
+ *        tracing. This debug string will only be held for the duration of the
+ *        method, and can be safely discarded after.
+ *
+ * @return 0 on success.
+ *         EINVAL if no hints were requested.
+ *         EBUSY if the hint was rate limited.
+ *         EPIPE if communication with the system service has failed.
+ *         ENOTSUP if the hint is not supported.
+ */
+int APerformanceHint_notifyWorkloadReset(
+        APerformanceHintSession* _Nonnull session,
+        bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
+
+/**
  * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
  * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
  * associated with it.
  *
- * @return AWorkDuration on success and nullptr otherwise.
+ * @return AWorkDuration pointer.
  */
 AWorkDuration* _Nonnull AWorkDuration_create() __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
- * Destroys {@link AWorkDuration} and free all resources associated to it.
+ * Destroys a {@link AWorkDuration} and frees all resources associated with it.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
  */
@@ -308,6 +412,114 @@
 void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t actualGpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
 
+/**
+ * Return the APerformanceHintSession wrapped by a Java PerformanceHintManager.Session object.
+ *
+ * The Java session maintains ownership over the wrapped native session, so it cannot be
+ * closed using {@link APerformanceHint_closeSession}.
+ *
+ * @param env The Java environment where the PerformanceHintManager.Session lives.
+ * @param sessionObj The Java Session to unwrap.
+ *
+ * @return A pointer to the APerformanceHintManager that backs the Java Session.
+ */
+APerformanceHintSession* _Nonnull APerformanceHint_borrowSessionFromJava(
+        JNIEnv* _Nonnull env, jobject _Nonnull sessionObj) __INTRODUCED_IN(36);
+
+/*
+ * Creates a new ASessionCreationConfig.
+ *
+ * When the client finishes using {@link ASessionCreationConfig}, it should
+ * call {@link ASessionCreationConfig_release()} to destroy
+ * {@link ASessionCreationConfig} and release all resources
+ * associated with it.
+ *
+ * @return ASessionCreationConfig pointer.
+ */
+ASessionCreationConfig* _Nonnull ASessionCreationConfig_create()
+                __INTRODUCED_IN(36);
+
+
+/**
+ * Destroys a {@link ASessionCreationConfig} and frees all
+ * resources associated with it.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ */
+void ASessionCreationConfig_release(
+                ASessionCreationConfig* _Nonnull config) __INTRODUCED_IN(36);
+
+/**
+ * Sets the tids to be associated with the session to be created.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}
+ * @param tids The list of tids to be associated with this session. They must be part of
+ *        this process' thread group.
+ * @param size The size of the list of tids.
+ *
+ * @return 0 on success.
+ *         EINVAL if invalid array pointer or the value of size
+ */
+int ASessionCreationConfig_setTids(
+        ASessionCreationConfig* _Nonnull config,
+        const pid_t* _Nonnull tids, size_t size)  __INTRODUCED_IN(36);
+
+/**
+ * Sets the initial target work duration in nanoseconds for the session to be created.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param targetWorkDurationNanos The parameter to specify a target duration
+ *        in nanoseconds for the new session; this value must be positive to use
+ *        the work duration API.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported
+ *         EINVAL if invalid value
+ */
+int ASessionCreationConfig_setTargetWorkDurationNanos(
+        ASessionCreationConfig* _Nonnull config,
+        int64_t targetWorkDurationNanos)  __INTRODUCED_IN(36);
+
+/**
+ * Sets whether power efficiency mode will be enabled for the session.
+ * This tells the session that these threads can be
+ * safely scheduled to prefer power efficiency over performance.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param enabled Whether power efficiency mode will be enabled.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported
+ *         EINVAL if invalid pointer to creation config
+ */
+int ASessionCreationConfig_setPreferPowerEfficiency(
+        ASessionCreationConfig* _Nonnull config, bool enabled)  __INTRODUCED_IN(36);
+
+/**
+ * Sessions setting this hint are expected to time the critical path of
+ * graphics pipeline from end to end, with the total work duration
+ * representing the time from the start of frame production until the
+ * buffer is fully finished drawing.
+ *
+ * It should include any threads on the critical path of that pipeline,
+ * up to a limit accessible from {@link getMaxGraphicsPipelineThreadsCount()}.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param enabled Whether this session manages a graphics pipeline's critical path.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported
+ *         EINVAL if invalid pointer to creation config or maximum threads for graphics
+                  pipeline is reached.
+ */
+int ASessionCreationConfig_setGraphicsPipeline(
+        ASessionCreationConfig* _Nonnull confi, bool enabled)  __INTRODUCED_IN(36);
+
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/audiomanager/IAudioManager.h b/include/audiomanager/IAudioManager.h
index 0b7e16b..a35a145 100644
--- a/include/audiomanager/IAudioManager.h
+++ b/include/audiomanager/IAudioManager.h
@@ -56,7 +56,7 @@
     /*oneway*/ virtual status_t playerAttributes(audio_unique_id_t piid, audio_usage_t usage,
                 audio_content_type_t content)= 0;
     /*oneway*/ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event,
-                audio_port_handle_t eventId) = 0;
+                const std::vector<audio_port_handle_t>& eventIds) = 0;
     /*oneway*/ virtual status_t releasePlayer(audio_unique_id_t piid) = 0;
     virtual audio_unique_id_t trackRecorder(const sp<IBinder>& recorder) = 0;
     /*oneway*/ virtual status_t recorderEvent(audio_unique_id_t riid, recorder_state_t event) = 0;
diff --git a/include/input/Input.h b/include/input/Input.h
index a8684bd..127046d 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -294,6 +294,8 @@
     NONE = AINPUT_KEYBOARD_TYPE_NONE,
     NON_ALPHABETIC = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
     ALPHABETIC = AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+    ftl_first = NONE,
+    ftl_last = ALPHABETIC,
 };
 
 bool isStylusToolType(ToolType toolType);
diff --git a/include/powermanager/OWNERS b/include/powermanager/OWNERS
new file mode 100644
index 0000000..9f40e27
--- /dev/null
+++ b/include/powermanager/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/ADPF_OWNERS
\ No newline at end of file
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index 7e0bd5b..f4f4d5e 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -72,6 +72,7 @@
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(
             int tgid, int uid) override;
     virtual HalResult<void> closeSessionChannel(int tgid, int uid) override;
+    virtual HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() override;
 
 private:
     std::mutex mConnectedHalMutex;
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index 6e347a9..4290182 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -63,6 +63,7 @@
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                        int uid) = 0;
     virtual HalResult<void> closeSessionChannel(int tgid, int uid) = 0;
+    virtual HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() = 0;
 };
 
 // Empty Power HAL wrapper that ignores all api calls.
@@ -85,6 +86,7 @@
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                int uid) override;
     HalResult<void> closeSessionChannel(int tgid, int uid) override;
+    HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() override;
 
 protected:
     virtual const char* getUnsupportedMessage();
@@ -170,6 +172,7 @@
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                int uid) override;
     HalResult<void> closeSessionChannel(int tgid, int uid) override;
+    HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() override;
 
 protected:
     const char* getUnsupportedMessage() override;
diff --git a/include/private/OWNERS b/include/private/OWNERS
new file mode 100644
index 0000000..37da96d
--- /dev/null
+++ b/include/private/OWNERS
@@ -0,0 +1,3 @@
+# ADPF
+per-file thermal_private.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file performance_hint_private.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index b7308c2..f150fb1 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -108,12 +108,46 @@
 APerformanceHintSession* APerformanceHint_createSessionInternal(APerformanceHintManager* manager,
                                         const int32_t* threadIds, size_t size,
                                         int64_t initialTargetWorkDurationNanos, SessionTag tag);
+/**
+ * Creates a session using ASessionCreationConfig
+ */
+APerformanceHintSession* APerformanceHint_createSessionUsingConfigInternal(
+        APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig,
+        SessionTag tag);
+
+/**
+ * Creates a session from the Java SDK implementation
+ */
+APerformanceHintSession* APerformanceHint_createSessionFromJava(APerformanceHintManager* manager,
+                                        const int32_t* threadIds, size_t size,
+                                        int64_t initialTargetWorkDurationNanos);
+
+/**
+ * Special method for Java SDK implementation to kill sessions
+ */
+void APerformanceHint_closeSessionFromJava(APerformanceHintSession* session);
 
 /**
  * Forces FMQ to be enabled or disabled, for testing only.
  */
 void APerformanceHint_setUseFMQForTesting(bool enabled);
 
+/**
+ * Get the rate limiter properties for testing.
+ */
+void APerformanceHint_getRateLimiterPropertiesForTesting(
+        int32_t* maxLoadHintsPerInterval, int64_t* loadHintInterval);
+
+/*
+ * Forces the "new load hint" flag to be disabled for testing.
+ */
+void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior);
+
+/*
+ * Forces the graphics pipeline flag to be enabled or disabled, for testing only.
+ */
+void APerformanceHint_setUseGraphicsPipelineForTesting(bool enabled);
+
 __END_DECLS
 
 #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 2ef642a..0a61178 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -829,6 +829,7 @@
     backend: {
         rust: {
             apex_available: [
+                "//apex_available:platform",
                 "com.android.virt",
             ],
             enabled: true,
@@ -881,7 +882,6 @@
         ":__subpackages__",
         "//packages/modules/Virtualization:__subpackages__",
         "//device/google/cuttlefish/shared/minidroid:__subpackages__",
-        "//system/software_defined_vehicle:__subpackages__",
         "//visibility:any_system_partition",
     ],
 }
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 174fe8a..4036551 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -27,7 +27,6 @@
     visibility: [
         "//device/google/cuttlefish/shared/minidroid/sample",
         "//packages/modules/Virtualization:__subpackages__",
-        "//system/software_defined_vehicle:__subpackages__",
     ],
     apex_available: [
         "//apex_available:platform",
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 17919c2..0580046 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -151,8 +151,10 @@
 
 int RpcServerTrusty::handleMessageInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
-    LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
-                        "bad state: message received on uninitialized channel");
+    if (channelContext == nullptr) {
+        LOG_RPC_DETAIL("bad state: message received on uninitialized channel");
+        return ERR_BAD_STATE;
+    }
 
     auto& session = channelContext->session;
     auto& connection = channelContext->connection;
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 8dabc2c..f9f304a 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -56,7 +56,7 @@
     ],
 
     export_shared_lib_headers: [
-        "android.hardware.graphics.common-V5-ndk",
+        "android.hardware.graphics.common-V6-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
     ],
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index d1a5663..a8d5fe7 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -92,7 +92,7 @@
     "android.hardware.common-V2-ndk.so:"
     "android.hardware.common.fmq-V1-ndk.so:"
     "android.hardware.graphics.allocator-V2-ndk.so:"
-    "android.hardware.graphics.common-V5-ndk.so:"
+    "android.hardware.graphics.common-V6-ndk.so:"
     "android.hardware.graphics.common@1.0.so:"
     "android.hardware.graphics.common@1.1.so:"
     "android.hardware.graphics.common@1.2.so:"
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index bfe3d6e..8566419 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -140,7 +140,7 @@
         BI_LOGE("Failed to addReleaseFenceLocked");
     }
 
-    err = releaseBufferLocked(slotIndex, buffer, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+    err = releaseBufferLocked(slotIndex, buffer);
     if (err != OK && err != IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
         BI_LOGE("Failed to release buffer: %s (%d)",
                 strerror(-err), err);
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index 95cce5c..f2173cd 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -314,7 +314,7 @@
             // so... basically, nothing more to do here.
         }
 
-        err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer);
         if (err < NO_ERROR) {
             GLC_LOGE("releaseTexImage: failed to release buffer: %s (%d)",
                     strerror(-err), err);
@@ -418,16 +418,14 @@
     if (!mAttached) {
         GLC_LOGE("updateAndRelease: GLConsumer is not attached to an OpenGL "
                 "ES context");
-        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                mEglDisplay, EGL_NO_SYNC_KHR);
+        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
         return INVALID_OPERATION;
     }
 
     // Confirm state.
     err = checkAndUpdateEglStateLocked();
     if (err != NO_ERROR) {
-        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                mEglDisplay, EGL_NO_SYNC_KHR);
+        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
         return err;
     }
 
@@ -440,8 +438,7 @@
     if (err != NO_ERROR) {
         GLC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d",
                 mEglDisplay, slot);
-        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                mEglDisplay, EGL_NO_SYNC_KHR);
+        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
         return UNKNOWN_ERROR;
     }
 
@@ -453,8 +450,7 @@
             // release the old buffer, so instead we just drop the new frame.
             // As we are still under lock since acquireBuffer, it is safe to
             // release by slot.
-            releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                    mEglDisplay, EGL_NO_SYNC_KHR);
+            releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
             return err;
         }
     }
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index c705d39..282957b 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -26,6 +26,7 @@
 
 #include <utils/NativeHandle.h>
 #include <utils/String8.h>
+#include <cstdint>
 
 namespace android {
 
@@ -84,7 +85,8 @@
                            EGLDisplay display __attribute__((unused)),
                            EGLSyncKHR fence __attribute__((unused)),
                            const sp<Fence>& releaseFence) override {
-        return callRemote<ReleaseBuffer>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
+        using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&);
+        return callRemote<Signature>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
     }
 
     status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) override {
@@ -188,8 +190,10 @@
             return callLocal(data, reply, &IGraphicBufferConsumer::detachBuffer);
         case Tag::ATTACH_BUFFER:
             return callLocal(data, reply, &IGraphicBufferConsumer::attachBuffer);
-        case Tag::RELEASE_BUFFER:
-            return callLocal(data, reply, &IGraphicBufferConsumer::releaseHelper);
+        case Tag::RELEASE_BUFFER: {
+            using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&);
+            return callLocal<Signature>(data, reply, &IGraphicBufferConsumer::releaseBuffer);
+        }
         case Tag::CONSUMER_CONNECT:
             return callLocal(data, reply, &IGraphicBufferConsumer::consumerConnect);
         case Tag::CONSUMER_DISCONNECT:
diff --git a/libs/gui/StreamSplitter.cpp b/libs/gui/StreamSplitter.cpp
index 2f8e104..653b91b 100644
--- a/libs/gui/StreamSplitter.cpp
+++ b/libs/gui/StreamSplitter.cpp
@@ -234,8 +234,7 @@
     LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
             "attaching buffer to input failed (%d)", status);
 
-    status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, tracker->getMergedFence());
+    status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0, tracker->getMergedFence());
     LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
             "releasing buffer to input failed (%d)", status);
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 61aabaa..7933776 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -3056,6 +3056,14 @@
     ComposerServiceAIDL::getComposerService()->setPowerMode(token, mode);
 }
 
+status_t SurfaceComposerClient::getMaxLayerPictureProfiles(const sp<IBinder>& token,
+                                                           int32_t* outMaxProfiles) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getMaxLayerPictureProfiles(token,
+                                                                                  outMaxProfiles);
+    return statusTFromBinderStatus(status);
+}
+
 status_t SurfaceComposerClient::getCompositionPreference(
         ui::Dataspace* defaultDataspace, ui::PixelFormat* defaultPixelFormat,
         ui::Dataspace* wideColorGamutDataspace, ui::PixelFormat* wideColorGamutPixelFormat) {
@@ -3280,6 +3288,13 @@
     return statusTFromBinderStatus(status);
 }
 
+status_t SurfaceComposerClient::setActivePictureListener(
+        const sp<gui::IActivePictureListener>& listener) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->setActivePictureListener(listener);
+    return statusTFromBinderStatus(status);
+}
+
 status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) {
     binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId);
     return statusTFromBinderStatus(status);
diff --git a/libs/gui/aidl/android/gui/ActivePicture.aidl b/libs/gui/aidl/android/gui/ActivePicture.aidl
new file mode 100644
index 0000000..9b83be1
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ActivePicture.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+/**
+ * Visible content that is using picture processing.
+ * @hide
+ */
+parcelable ActivePicture {
+    /** The layer ID that is using picture processing. */
+    int layerId;
+
+    /** UID that owns layer using picture processing. */
+    int ownerUid;
+
+    /** ID of the picture profile that was used to configure the picture processing. */
+    long pictureProfileId;
+}
diff --git a/libs/gui/aidl/android/gui/IActivePictureListener.aidl b/libs/gui/aidl/android/gui/IActivePictureListener.aidl
new file mode 100644
index 0000000..ad310bb
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IActivePictureListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+import android.gui.ActivePicture;
+
+/**
+ * Receive callbacks whenever the visible content using picture profiles changes.
+ * @hide
+ */
+interface IActivePictureListener {
+    /**
+     * Callback reporting the visible content on the screen using picture profiles.
+     */
+    oneway void onActivePicturesChanged(in ActivePicture[] activePictures);
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index ac14138..8c19bbb 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -33,6 +33,7 @@
 import android.gui.FrameStats;
 import android.gui.HdrConversionCapability;
 import android.gui.HdrConversionStrategy;
+import android.gui.IActivePictureListener;
 import android.gui.IDisplayEventConnection;
 import android.gui.IFpsListener;
 import android.gui.IHdrLayerInfoListener;
@@ -228,6 +229,11 @@
     void setGameContentType(IBinder display, boolean on);
 
     /**
+     * Gets the maximum number of picture profiles supported by the display.
+     */
+    int getMaxLayerPictureProfiles(IBinder display);
+
+    /**
      * Capture the specified screen. This requires READ_FRAME_BUFFER
      * permission.  This function will fail if there is a secure window on
      * screen and DisplayCaptureArgs.captureSecureLayers is false.
@@ -599,4 +605,10 @@
      * past the provided VSync.
      */
     oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync);
+
+    /**
+     * Sets the listener used to monitor visible content that is being processed with picture
+     * profiles.
+     */
+    oneway void setActivePictureListener(IActivePictureListener listener);
 }
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index bfe3eb3..8a66dc0 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -268,9 +268,9 @@
 
     // releaseBufferLocked overrides the ConsumerBase method to update the
     // mEglSlots array in addition to the ConsumerBase.
-    virtual status_t releaseBufferLocked(int slot,
-            const sp<GraphicBuffer> graphicBuffer,
-            EGLDisplay display, EGLSyncKHR eglFence) override;
+    virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
+                                         EGLDisplay display = EGL_NO_DISPLAY,
+                                         EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
 
     status_t releaseBufferLocked(int slot,
             const sp<GraphicBuffer> graphicBuffer, EGLSyncKHR eglFence) {
diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h
index 0b92e7d..18f5488 100644
--- a/libs/gui/include/gui/IGraphicBufferConsumer.h
+++ b/libs/gui/include/gui/IGraphicBufferConsumer.h
@@ -137,16 +137,9 @@
     virtual status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display,
                                    EGLSyncKHR fence, const sp<Fence>& releaseFence) = 0;
 
-    status_t releaseHelper(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
+    status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
         return releaseBuffer(buf, frameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence);
     }
-    // This is explicitly *not* the actual signature of IGBC::releaseBuffer, but:
-    //     1) We have no easy way to send the EGL objects across Binder
-    //     2) This has always been broken, probably because
-    //     3) IGBC is rarely remoted
-    // For now, we will choose to bury our heads in the sand and ignore this problem until such time
-    // as we can finally finish converting away from EGL sync to native Android sync
-    using ReleaseBuffer = decltype(&IGraphicBufferConsumer::releaseHelper);
 
     // consumerConnect connects a consumer to the BufferQueue. Only one consumer may be connected,
     // and when that consumer disconnects the BufferQueue is placed into the "abandoned" state,
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0d7f8c2..0ce0c0a 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -298,6 +298,8 @@
     static status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
                                                const sp<gui::IHdrLayerInfoListener>& listener);
 
+    static status_t setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
     /*
      * Sends a power boost to the composer. This function is asynchronous.
      *
@@ -343,6 +345,15 @@
 
     static bool flagEdgeExtensionEffectUseShader();
 
+    /**
+     * Returns how many picture profiles are supported by the display.
+     *
+     * displayToken
+     *      The token of the display.
+     */
+    static status_t getMaxLayerPictureProfiles(const sp<IBinder>& displayToken,
+                                               int32_t* outMaxProfiles);
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index 22d32e9..6bf38c0 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -131,3 +131,11 @@
   bug: "359252619"
   is_fixed_read_only: true
 } # bq_producer_throttles_only_async_mode
+
+flag {
+  name: "bq_gl_fence_cleanup"
+  namespace: "core_graphics"
+  description: "Remove BufferQueueProducer::dequeue's wait on this fence (or the fence entirely) to prevent deadlocks"
+  bug: "339705065"
+  is_fixed_read_only: true
+} # bq_gl_fence_cleanup
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index b026e64..1606099 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -425,8 +425,7 @@
     ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(&newSlot, nullptr));
     ASSERT_EQ(OK, mConsumer->attachBuffer(&newSlot, item.mGraphicBuffer));
 
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, EGL_NO_DISPLAY,
-            EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, Fence::NO_FENCE));
 
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
               mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -609,8 +608,7 @@
     ASSERT_EQ(true, item.mQueuedBuffer);
     ASSERT_EQ(false, item.mAutoRefresh);
 
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // attempt to acquire a second time should return no buffer available
     ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
@@ -653,8 +651,7 @@
         ASSERT_EQ(i == 0, item.mQueuedBuffer);
         ASSERT_EQ(true, item.mAutoRefresh);
 
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     }
 
     // Repeatedly queue and dequeue a buffer from the producer side, it should
@@ -684,8 +681,7 @@
         ASSERT_EQ(i == 0, item.mQueuedBuffer);
         ASSERT_EQ(true, item.mAutoRefresh);
 
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     }
 }
 
@@ -735,8 +731,7 @@
     ASSERT_EQ(true, item.mQueuedBuffer);
     ASSERT_EQ(false, item.mAutoRefresh);
 
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // attempt to acquire a second time should return no buffer available
     ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
@@ -874,8 +869,7 @@
     for (size_t i = 0; i < 2; ++i) {
         BufferItem item;
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     }
 
     // Make sure we got the second buffer back
@@ -929,8 +923,7 @@
                                            nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
 
@@ -946,8 +939,7 @@
                                            nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
     ASSERT_EQ(OK,
@@ -959,12 +951,10 @@
                                        nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     std::this_thread::sleep_for(16ms);
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Sleep between segments
     std::this_thread::sleep_for(500ms);
@@ -981,13 +971,11 @@
                                            nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Now we read the segments
     std::vector<OccupancyTracker::Segment> history;
@@ -1108,8 +1096,7 @@
 
     // Acquire and free 1 buffer
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     int releasedSlot = item.mSlot;
 
     // Acquire 1 buffer, leaving 1 filled buffer in queue
@@ -1376,8 +1363,7 @@
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
     ASSERT_EQ(slot, item.mSlot);
     ASSERT_NE(nullptr, item.mGraphicBuffer.get());
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Dequeue and queue the buffer again
     ASSERT_EQ(OK,
@@ -1390,8 +1376,7 @@
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
     ASSERT_EQ(slot, item.mSlot);
     ASSERT_EQ(nullptr, item.mGraphicBuffer.get());
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Dequeue and queue the buffer again
     ASSERT_EQ(OK,
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
index f34b03e..1c439cd 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -95,8 +95,7 @@
     ASSERT_EQ(*dataOut, TEST_DATA);
     ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
 
-    ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // This should succeed even with allocation disabled since it will have
     // received the buffer back from the output BufferQueue
@@ -168,9 +167,9 @@
         ASSERT_EQ(*dataOut, TEST_DATA);
         ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
 
-        ASSERT_EQ(OK, outputConsumers[output]->releaseBuffer(item.mSlot,
-                    item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
-                    Fence::NO_FENCE));
+        ASSERT_EQ(OK,
+                  outputConsumers[output]->releaseBuffer(item.mSlot, item.mFrameNumber,
+                                                         Fence::NO_FENCE));
     }
 
     // This should succeed even with allocation disabled since it will have
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 88893b6..76362ff 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -21,6 +21,7 @@
 #include <gtest/gtest.h>
 
 #include <SurfaceFlingerProperties.h>
+#include <android/gui/IActivePictureListener.h>
 #include <android/gui/IDisplayEventConnection.h>
 #include <android/gui/ISurfaceComposer.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
@@ -201,9 +202,9 @@
         releasedItems.resize(1+extraDiscardedBuffers);
         for (size_t i = 0; i < releasedItems.size(); i++) {
             ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0));
-            ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot,
-                    releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
-                    Fence::NO_FENCE));
+            ASSERT_EQ(NO_ERROR,
+                      consumer->releaseBuffer(releasedItems[i].mSlot, releasedItems[i].mFrameNumber,
+                                              Fence::NO_FENCE));
         }
         int32_t expectedReleaseCb = (enableReleasedCb ? releasedItems.size() : 0);
         if (hasSurfaceListener) {
@@ -1015,6 +1016,15 @@
         return binder::Status::ok();
     }
 
+    binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>&) {
+        return binder::Status::ok();
+    }
+
+    binder::Status getMaxLayerPictureProfiles(const sp<IBinder>& /*display*/,
+                                              int32_t* /*outMaxProfiles*/) {
+        return binder::Status::ok();
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index a2bb345..b87a706 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -705,15 +705,17 @@
 const PointerCoords* MotionEvent::getHistoricalRawPointerCoords(
         size_t pointerIndex, size_t historicalIndex) const {
     if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) {
-        LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this;
+        LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex
+                   << "; should be between >0 and ≤" << getPointerCount();
     }
     if (CC_UNLIKELY(historicalIndex < 0 || historicalIndex > getHistorySize())) {
-        LOG(FATAL) << __func__ << ": Invalid historical index " << historicalIndex << " for "
-                   << *this;
+        LOG(FATAL) << __func__ << ": Invalid historical index " << historicalIndex
+                   << "; should be >0 and ≤" << getHistorySize();
     }
     const size_t position = historicalIndex * getPointerCount() + pointerIndex;
     if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) {
-        LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this;
+        LOG(FATAL) << __func__ << ": Invalid array index " << position << "; should be >0 and ≤"
+                   << mSamplePointerCoords.size();
     }
     return &mSamplePointerCoords[position];
 }
diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs
index 6bdcefd..beb6e23 100644
--- a/libs/input/rust/data_store.rs
+++ b/libs/input/rust/data_store.rs
@@ -17,7 +17,7 @@
 //! Contains the DataStore, used to store input related data in a persistent way.
 
 use crate::input::KeyboardType;
-use log::{debug, error};
+use log::{debug, error, info};
 use serde::{Deserialize, Serialize};
 use std::fs::File;
 use std::io::{Read, Write};
@@ -157,7 +157,7 @@
         let path = Path::new(&self.filepath);
         let mut fs_string = String::new();
         match File::open(path) {
-            Err(e) => error!("couldn't open {:?}: {}", path, e),
+            Err(e) => info!("couldn't open {:?}: {}", path, e),
             Ok(mut file) => match file.read_to_string(&mut fs_string) {
                 Err(e) => error!("Couldn't read from {:?}: {}", path, e),
                 Ok(_) => debug!("Successfully read from file {:?}", path),
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index 1210f71..39bb841 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
+#include <TestEventMatchers.h>
 #include <android-base/logging.h>
 #include <attestation/HmacKeyManager.h>
 #include <ftl/enum.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/BlockingQueue.h>
 #include <input/InputConsumerNoResampling.h>
 #include <input/InputTransport.h>
 
 using android::base::Result;
+using ::testing::Matcher;
 
 namespace android {
 
@@ -278,7 +281,7 @@
     void SetUp() override {
         std::unique_ptr<InputChannel> serverChannel;
         status_t result =
-                InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+                InputChannel::openInputChannelPair("test channel", serverChannel, mClientChannel);
         ASSERT_EQ(OK, result);
 
         mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
@@ -336,6 +339,8 @@
     // accessed on the test thread.
     BlockingQueue<bool> mProbablyHasInputResponses;
 
+    std::unique_ptr<MotionEvent> assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher);
+
 private:
     sp<MessageHandler> mMessageHandler;
     void handleMessage(const Message& message);
@@ -389,6 +394,20 @@
     mLooper->sendMessage(mMessageHandler, msg);
 }
 
+std::unique_ptr<MotionEvent> InputPublisherAndConsumerNoResamplingTest::assertReceivedMotionEvent(
+        const Matcher<MotionEvent>& matcher) {
+    std::optional<std::unique_ptr<MotionEvent>> event = mMotionEvents.popWithTimeout(TIMEOUT);
+    if (!event) {
+        ADD_FAILURE() << "No event was received, but expected motion " << matcher;
+        return nullptr;
+    }
+    if (*event == nullptr) {
+        LOG(FATAL) << "Event was received, but it was null";
+    }
+    EXPECT_THAT(**event, matcher);
+    return std::move(*event);
+}
+
 void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
     switch (static_cast<LooperMessage>(message.what)) {
         case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
@@ -572,8 +591,7 @@
     const nsecs_t publishTimeOfDown = systemTime(SYSTEM_TIME_MONOTONIC);
     publishMotionEvent(*mPublisher, argsDown);
 
-    // Consume the DOWN event.
-    ASSERT_TRUE(mMotionEvents.popWithTimeout(TIMEOUT).has_value());
+    assertReceivedMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
     verifyFinishedSignal(*mPublisher, mSeq, publishTimeOfDown);
 
@@ -622,10 +640,10 @@
     // the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a
     // mix of those)
     while (singleSampledMotionEvents.size() != nSamples) {
-        const std::optional<std::unique_ptr<MotionEvent>> batchedMotionEvent =
-                mMotionEvents.popWithTimeout(TIMEOUT);
+        const std::unique_ptr<MotionEvent> batchedMotionEvent =
+                assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
         // The events received by these calls are never null
-        std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(**batchedMotionEvent);
+        std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(*batchedMotionEvent);
         singleSampledMotionEvents.insert(singleSampledMotionEvents.end(), splitMotionEvents.begin(),
                                          splitMotionEvents.end());
     }
@@ -681,10 +699,7 @@
     }
     mNotifyLooperMayProceed.notify_all();
 
-    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(optMotion.has_value());
-    std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
-    ASSERT_EQ(ACTION_MOVE, motion->getAction());
+    assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
 
     verifyFinishedSignal(*mPublisher, seq, publishTime);
 }
@@ -696,9 +711,7 @@
     nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
     publishMotionEvent(*mPublisher, args);
 
-    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(optMotion.has_value());
-    std::unique_ptr<MotionEvent> event = std::move(*optMotion);
+    std::unique_ptr<MotionEvent> event = assertReceivedMotionEvent(WithMotionAction(action));
 
     verifyArgsEqualToEvent(args, *event);
 
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index f1453bd..006a785 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -344,7 +344,8 @@
      * mEglSlots array in addition to the ConsumerBase.
      */
     virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
-                                         EGLDisplay display, EGLSyncKHR eglFence) override;
+                                         EGLDisplay display = EGL_NO_DISPLAY,
+                                         EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
 
     /**
      * freeBufferLocked frees up the given buffer slot. If the slot has been
diff --git a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
index 275b7a4..3959fce 100644
--- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
@@ -150,8 +150,7 @@
             }
         }
 
-        err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer, mEglDisplay,
-                                     EGL_NO_SYNC_KHR);
+        err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer);
         if (err < NO_ERROR) {
             EGC_LOGE("releaseTexImage: failed to release buffer: %s (%d)", strerror(-err), err);
             return err;
@@ -234,14 +233,14 @@
     if (st.mOpMode != SurfaceTexture::OpMode::attachedToGL) {
         EGC_LOGE("updateAndRelease: EGLConsumer is not attached to an OpenGL "
                  "ES context");
-        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
         return INVALID_OPERATION;
     }
 
     // Confirm state.
     err = checkAndUpdateEglStateLocked(st);
     if (err != NO_ERROR) {
-        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
         return err;
     }
 
@@ -254,7 +253,7 @@
     if (err != NO_ERROR) {
         EGC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay,
                  slot);
-        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
         return UNKNOWN_ERROR;
     }
 
@@ -266,8 +265,7 @@
             // release the old buffer, so instead we just drop the new frame.
             // As we are still under lock since acquireBuffer, it is safe to
             // release by slot.
-            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay,
-                                   EGL_NO_SYNC_KHR);
+            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
             return err;
         }
     }
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index 32b229d..60e87b5 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -64,8 +64,7 @@
         // Wait on the producer fence for the buffer to be ready.
         err = fenceWait(item.mFence->get(), fencePassThroughHandle);
         if (err != OK) {
-            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
-                                   EGL_NO_SYNC_KHR);
+            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
             return nullptr;
         }
     }
@@ -79,8 +78,7 @@
         err = createFence(st.mUseFenceSync, &mImageSlots[slot].eglFence(), &display,
                           &releaseFenceId, fencePassThroughHandle);
         if (OK != err) {
-            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
-                                   EGL_NO_SYNC_KHR);
+            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
             return nullptr;
         }
 
@@ -91,8 +89,7 @@
                                                     releaseFence);
             if (err != OK) {
                 IMG_LOGE("dequeueImage: error adding release fence: %s (%d)", strerror(-err), err);
-                st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
-                                       EGL_NO_SYNC_KHR);
+                st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
                 return nullptr;
             }
         }
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 014c912..9876362 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -31,11 +31,13 @@
 };
 use ffi::{
     AHardwareBuffer, AHardwareBuffer_Desc, AHardwareBuffer_readFromParcel,
-    AHardwareBuffer_writeToParcel,
+    AHardwareBuffer_writeToParcel, ARect,
 };
+use std::ffi::c_void;
 use std::fmt::{self, Debug, Formatter};
-use std::mem::ManuallyDrop;
-use std::ptr::{self, null_mut, NonNull};
+use std::mem::{forget, ManuallyDrop};
+use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
+use std::ptr::{self, null, null_mut, NonNull};
 
 /// Wrapper around a C `AHardwareBuffer_Desc`.
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -267,10 +269,141 @@
             rfu0: 0,
             rfu1: 0,
         };
-        // SAFETY: neither the buffer nor AHardwareBuffer_Desc pointers will be null.
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the
+        // AHardwareBuffer_Desc pointer is valid because it comes from a reference.
         unsafe { ffi::AHardwareBuffer_describe(self.0.as_ref(), &mut buffer_desc) };
         HardwareBufferDescription(buffer_desc)
     }
+
+    /// Locks the hardware buffer for direct CPU access.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    /// - If `rect` is not `None`, the caller must not modify the buffer outside of that rectangle.
+    pub unsafe fn lock<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<HardwareBufferGuard<'a>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut address = null_mut();
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the buffer address out
+        // pointer is valid because it comes from a reference. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lock(self.0.as_ptr(), usage.0, fence, rect, &mut address)
+        };
+        status_result(status)?;
+        Ok(HardwareBufferGuard {
+            buffer: self,
+            address: NonNull::new(address)
+                .expect("AHardwareBuffer_lock set a null outVirtualAddress"),
+        })
+    }
+
+    /// Locks the hardware buffer for direct CPU access, returning information about the bytes per
+    /// pixel and stride as well.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    pub unsafe fn lock_and_get_info<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<LockedBufferInfo<'a>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut address = null_mut();
+        let mut bytes_per_pixel = 0;
+        let mut stride = 0;
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the various out
+        // pointers are valid because they come from references. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lockAndGetInfo(
+                self.0.as_ptr(),
+                usage.0,
+                fence,
+                rect,
+                &mut address,
+                &mut bytes_per_pixel,
+                &mut stride,
+            )
+        };
+        status_result(status)?;
+        Ok(LockedBufferInfo {
+            guard: HardwareBufferGuard {
+                buffer: self,
+                address: NonNull::new(address)
+                    .expect("AHardwareBuffer_lockAndGetInfo set a null outVirtualAddress"),
+            },
+            bytes_per_pixel: bytes_per_pixel as u32,
+            stride: stride as u32,
+        })
+    }
+
+    /// Unlocks the hardware buffer from direct CPU access.
+    ///
+    /// Must be called after all changes to the buffer are completed by the caller. This will block
+    /// until the unlocking is complete and the buffer contents are updated.
+    fn unlock(&self) -> Result<(), StatusCode> {
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid.
+        let status = unsafe { ffi::AHardwareBuffer_unlock(self.0.as_ptr(), null_mut()) };
+        status_result(status)?;
+        Ok(())
+    }
+
+    /// Unlocks the hardware buffer from direct CPU access.
+    ///
+    /// Must be called after all changes to the buffer are completed by the caller.
+    ///
+    /// This may not block until all work is completed, but rather will return a file descriptor
+    /// which will be signalled once the unlocking is complete and the buffer contents is updated.
+    /// If `Ok(None)` is returned then unlocking has already completed and no further waiting is
+    /// necessary. The file descriptor may be passed to a subsequent call to [`Self::lock`].
+    pub fn unlock_with_fence(
+        &self,
+        guard: HardwareBufferGuard,
+    ) -> Result<Option<OwnedFd>, StatusCode> {
+        // Forget the guard so that its `Drop` implementation doesn't try to unlock the
+        // HardwareBuffer again.
+        forget(guard);
+
+        let mut fence = -2;
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid.
+        let status = unsafe { ffi::AHardwareBuffer_unlock(self.0.as_ptr(), &mut fence) };
+        let fence = if fence < 0 {
+            None
+        } else {
+            // SAFETY: `AHardwareBuffer_unlock` gives us ownership of the fence file descriptor.
+            Some(unsafe { OwnedFd::from_raw_fd(fence) })
+        };
+        status_result(status)?;
+        Ok(fence)
+    }
 }
 
 impl Drop for HardwareBuffer {
@@ -346,6 +479,37 @@
 //     according to the docs on the underlying gralloc calls)
 unsafe impl Sync for HardwareBuffer {}
 
+/// A guard for when a `HardwareBuffer` is locked.
+///
+/// The `HardwareBuffer` will be unlocked when this is dropped, or may be unlocked via
+/// [`HardwareBuffer::unlock_with_fence`].
+#[derive(Debug)]
+pub struct HardwareBufferGuard<'a> {
+    buffer: &'a HardwareBuffer,
+    /// The address of the buffer in memory.
+    pub address: NonNull<c_void>,
+}
+
+impl<'a> Drop for HardwareBufferGuard<'a> {
+    fn drop(&mut self) {
+        self.buffer
+            .unlock()
+            .expect("Failed to unlock HardwareBuffer when dropping HardwareBufferGuard");
+    }
+}
+
+/// A guard for when a `HardwareBuffer` is locked, with additional information about the number of
+/// bytes per pixel and stride.
+#[derive(Debug)]
+pub struct LockedBufferInfo<'a> {
+    /// The locked buffer guard.
+    pub guard: HardwareBufferGuard<'a>,
+    /// The number of bytes used for each pixel in the buffer.
+    pub bytes_per_pixel: u32,
+    /// The stride in bytes between rows in the buffer.
+    pub stride: u32,
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -499,4 +663,108 @@
         assert_eq!(buffer.description(), buffer_description);
         assert_eq!(buffer2.description(), buffer_description);
     }
+
+    #[test]
+    fn lock() {
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let guard = unsafe {
+            buffer.lock(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                None,
+            )
+        }
+        .unwrap();
+
+        drop(guard);
+    }
+
+    #[test]
+    fn lock_with_rect() {
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+        let rect = ARect { left: 10, right: 20, top: 35, bottom: 45 };
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let guard = unsafe {
+            buffer.lock(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                Some(&rect),
+            )
+        }
+        .unwrap();
+
+        drop(guard);
+    }
+
+    #[test]
+    fn unlock_with_fence() {
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let guard = unsafe {
+            buffer.lock(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                None,
+            )
+        }
+        .unwrap();
+
+        buffer.unlock_with_fence(guard).unwrap();
+    }
+
+    #[test]
+    fn lock_with_info() {
+        const WIDTH: u32 = 1024;
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            WIDTH,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let info = unsafe {
+            buffer.lock_and_get_info(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                None,
+            )
+        }
+        .unwrap();
+
+        assert_eq!(info.bytes_per_pixel, 4);
+        assert_eq!(info.stride, WIDTH * 4);
+        drop(info);
+    }
 }
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index 8b13d78..8d6f74b 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -19,9 +19,12 @@
 
 #include <algorithm>
 #include <cctype>
+#include <cstdint>
 #include <numeric>
 #include <optional>
 #include <span>
+#include <string>
+#include <string_view>
 
 #include <ftl/hash.h>
 #include <log/log.h>
@@ -194,6 +197,21 @@
     const uint16_t productId =
             static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
 
+    //   Bytes 12-15: display serial number, in little-endian (LSB). This field is
+    //   optional and its absence is marked by having all bytes set to 0x00.
+    //   Values do not represent ASCII characters.
+    constexpr size_t kSerialNumberOffset = 12;
+    if (edid.size() < kSerialNumberOffset + sizeof(uint32_t)) {
+        ALOGE("Invalid EDID: block zero S/N is truncated.");
+        return {};
+    }
+    const uint32_t blockZeroSerialNumber = edid[kSerialNumberOffset] +
+            (edid[kSerialNumberOffset + 1] << 8) + (edid[kSerialNumberOffset + 2] << 16) +
+            (edid[kSerialNumberOffset + 3] << 24);
+    const auto hashedBlockZeroSNOpt = blockZeroSerialNumber == 0
+            ? std::nullopt
+            : ftl::stable_hash(std::string_view(std::to_string(blockZeroSerialNumber)));
+
     constexpr size_t kManufactureWeekOffset = 16;
     if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) {
         ALOGE("Invalid EDID: manufacture week is truncated.");
@@ -212,6 +230,15 @@
     ALOGW_IF(manufactureOrModelYear <= 0xf,
              "Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf].");
 
+    constexpr size_t kMaxHorizontalPhysicalSizeOffset = 21;
+    constexpr size_t kMaxVerticalPhysicalSizeOffset = 22;
+    if (edid.size() < kMaxVerticalPhysicalSizeOffset + sizeof(uint8_t)) {
+        ALOGE("Invalid EDID: display's physical size is truncated.");
+        return {};
+    }
+    ui::Size maxPhysicalSizeInCm(edid[kMaxHorizontalPhysicalSizeOffset],
+                                 edid[kMaxVerticalPhysicalSizeOffset]);
+
     constexpr size_t kDescriptorOffset = 54;
     if (edid.size() < kDescriptorOffset) {
         ALOGE("Invalid EDID: descriptors are missing.");
@@ -222,7 +249,8 @@
     view = view.subspan(kDescriptorOffset);
 
     std::string_view displayName;
-    std::string_view serialNumber;
+    std::string_view descriptorBlockSerialNumber;
+    std::optional<uint64_t> hashedDescriptorBlockSNOpt = std::nullopt;
     std::string_view asciiText;
     ui::Size preferredDTDPixelSize;
     ui::Size preferredDTDPhysicalSize;
@@ -247,7 +275,10 @@
                     asciiText = parseEdidText(descriptor);
                     break;
                 case 0xff:
-                    serialNumber = parseEdidText(descriptor);
+                    descriptorBlockSerialNumber = parseEdidText(descriptor);
+                    hashedDescriptorBlockSNOpt = descriptorBlockSerialNumber.empty()
+                            ? std::nullopt
+                            : ftl::stable_hash(descriptorBlockSerialNumber);
                     break;
             }
         } else if (isDetailedTimingDescriptor(view)) {
@@ -288,7 +319,7 @@
 
     if (modelString.empty()) {
         ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
-        modelString = serialNumber;
+        modelString = descriptorBlockSerialNumber;
     }
     if (modelString.empty()) {
         ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
@@ -341,11 +372,14 @@
     return Edid{
             .manufacturerId = manufacturerId,
             .productId = productId,
+            .hashedBlockZeroSerialNumberOpt = hashedBlockZeroSNOpt,
+            .hashedDescriptorBlockSerialNumberOpt = hashedDescriptorBlockSNOpt,
             .pnpId = *pnpId,
             .modelHash = modelHash,
             .displayName = displayName,
             .manufactureOrModelYear = manufactureOrModelYear,
             .manufactureWeek = manufactureWeek,
+            .physicalSizeInCm = maxPhysicalSizeInCm,
             .cea861Block = cea861Block,
             .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
     };
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index 648e024..cf67d7b 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -69,11 +69,14 @@
 struct Edid {
     uint16_t manufacturerId;
     uint16_t productId;
+    std::optional<uint64_t> hashedBlockZeroSerialNumberOpt;
+    std::optional<uint64_t> hashedDescriptorBlockSerialNumberOpt;
     PnpId pnpId;
     uint32_t modelHash;
     std::string_view displayName;
     uint8_t manufactureOrModelYear;
     uint8_t manufactureWeek;
+    ui::Size physicalSizeInCm;
     std::optional<Cea861ExtensionBlock> cea861Block;
     std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
diff --git a/libs/ui/include_types/ui/HdrRenderTypeUtils.h b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
index b0af878..70c50f0 100644
--- a/libs/ui/include_types/ui/HdrRenderTypeUtils.h
+++ b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
@@ -61,4 +61,24 @@
     return HdrRenderType::SDR;
 }
 
-} // namespace android
\ No newline at end of file
+/**
+ * Returns the maximum headroom allowed for this content under "idealized"
+ * display conditions (low surround luminance, high-enough display brightness).
+ *
+ * TODO: take into account hdr metadata, but square it with the fact that some
+ * HLG content has CTA.861-3 metadata
+ */
+inline float getIdealizedMaxHeadroom(ui::Dataspace dataspace) {
+    const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+    switch (transfer) {
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            return 10000.0f / 203.0f;
+        case HAL_DATASPACE_TRANSFER_HLG:
+            return 1000.0f / 203.0f;
+        default:
+            return 1.0f;
+    }
+}
+
+} // namespace android
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 76e3f66..d1699e7 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -33,7 +33,7 @@
 namespace {
 
 const unsigned char kInternalEdid[] =
-        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x4e\x61\xbc\x00"
         "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
         "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
         "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
@@ -54,7 +54,7 @@
 
 // Extended EDID with timing extension.
 const unsigned char kExternalEedid[] =
-        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00"
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\xb1\x7f\x39\x05"
         "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26"
         "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00"
         "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
@@ -112,7 +112,7 @@
         "\x07";
 
 const unsigned char kCtlDisplayEdid[] =
-        "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x00\x00\x00\x00"
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x30\x41\xab\x00"
         "\xff\x17\x01\x04\xa5\x34\x1d\x78\x3a\xa7\x25\xa4\x57\x51\xa0\x26"
         "\x10\x50\x54\xbf\xef\x80\xb3\x00\xa9\x40\x95\x00\x81\x40\x81\x80"
         "\x95\x0f\x71\x4f\x90\x40\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
@@ -191,8 +191,13 @@
     EXPECT_EQ(hash("121AT11-801"), 626564263);
     EXPECT_TRUE(edid->displayName.empty());
     EXPECT_EQ(12610, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("12345678"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(21, edid->manufactureOrModelYear);
     EXPECT_EQ(0, edid->manufactureWeek);
+    EXPECT_EQ(26, edid->physicalSizeInCm.width);
+    EXPECT_EQ(16, edid->physicalSizeInCm.height);
     EXPECT_FALSE(edid->cea861Block);
     EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
     EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
@@ -207,8 +212,14 @@
     EXPECT_EQ(hash("HP ZR30w"), 918492362);
     EXPECT_EQ("HP ZR30w", edid->displayName);
     EXPECT_EQ(10348, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_TRUE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("CN4202137Q"), edid->hashedDescriptorBlockSerialNumberOpt.value());
     EXPECT_EQ(22, edid->manufactureOrModelYear);
     EXPECT_EQ(2, edid->manufactureWeek);
+    EXPECT_EQ(64, edid->physicalSizeInCm.width);
+    EXPECT_EQ(40, edid->physicalSizeInCm.height);
     EXPECT_FALSE(edid->cea861Block);
     EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
     EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
@@ -223,8 +234,13 @@
     EXPECT_EQ(hash("SAMSUNG"), 1201368132);
     EXPECT_EQ("SAMSUNG", edid->displayName);
     EXPECT_EQ(2302, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("87654321"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(21, edid->manufactureOrModelYear);
     EXPECT_EQ(41, edid->manufactureWeek);
+    EXPECT_EQ(16, edid->physicalSizeInCm.width);
+    EXPECT_EQ(9, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
     auto physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -245,8 +261,13 @@
     EXPECT_EQ(hash("Panasonic-TV"), 3876373262);
     EXPECT_EQ("Panasonic-TV", edid->displayName);
     EXPECT_EQ(41622, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(29, edid->manufactureOrModelYear);
     EXPECT_EQ(0, edid->manufactureWeek);
+    EXPECT_EQ(128, edid->physicalSizeInCm.width);
+    EXPECT_EQ(72, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
     physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -267,8 +288,12 @@
     EXPECT_EQ(hash("Hisense"), 2859844809);
     EXPECT_EQ("Hisense", edid->displayName);
     EXPECT_EQ(0, edid->productId);
+    EXPECT_FALSE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(29, edid->manufactureOrModelYear);
     EXPECT_EQ(18, edid->manufactureWeek);
+    EXPECT_EQ(0, edid->physicalSizeInCm.width);
+    EXPECT_EQ(0, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
     physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -289,8 +314,13 @@
     EXPECT_EQ(hash("LP2361"), 1523181158);
     EXPECT_EQ("LP2361", edid->displayName);
     EXPECT_EQ(9373, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("11223344"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(23, edid->manufactureOrModelYear);
     EXPECT_EQ(0xff, edid->manufactureWeek);
+    EXPECT_EQ(52, edid->physicalSizeInCm.width);
+    EXPECT_EQ(29, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock);
     EXPECT_EQ(1360, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
@@ -447,4 +477,4 @@
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp
index da1aae2..f8a38d1 100644
--- a/services/audiomanager/IAudioManager.cpp
+++ b/services/audiomanager/IAudioManager.cpp
@@ -87,12 +87,15 @@
     }
 
     virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event,
-            audio_port_handle_t eventId) {
+            const std::vector<audio_port_handle_t>& eventIds) {
         Parcel data, reply;
         data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor());
         data.writeInt32((int32_t) piid);
         data.writeInt32((int32_t) event);
-        data.writeInt32((int32_t) eventId);
+        data.writeInt32((int32_t) eventIds.size());
+        for (auto eventId: eventIds) {
+            data.writeInt32((int32_t) eventId);
+        }
         return remote()->transact(PLAYER_EVENT, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpuWork.c b/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
index f470189..94abc69 100644
--- a/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
+++ b/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
@@ -20,11 +20,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#ifdef MOCK_BPF
-#include <test/mock_bpf_helpers.h>
-#else
 #include <bpf_helpers.h>
-#endif
 
 #define S_IN_NS (1000000000)
 #define SMALL_TIME_GAP_LIMIT_NS (S_IN_NS)
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 305feab..580cde3 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -362,6 +362,9 @@
     /* Toggle Caps Lock */
     virtual void toggleCapsLockState(int32_t deviceId) = 0;
 
+    /* Resets locked modifier state */
+    virtual void resetLockedModifierState() = 0;
+
     /* Determine whether physical keys exist for the given framework-domain key codes. */
     virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
                          const std::vector<int32_t>& keyCodes, uint8_t* outFlags) = 0;
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index a2b7e82..24919b6 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -19,6 +19,7 @@
 #include "InputReader.h"
 
 #include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
 #include <errno.h>
 #include <input/Keyboard.h>
 #include <input/VirtualKeyMap.h>
@@ -589,6 +590,11 @@
     }
 }
 
+void InputReader::resetLockedModifierState() {
+    std::scoped_lock _l(mLock);
+    updateLedMetaStateLocked(0);
+}
+
 bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
                           const std::vector<int32_t>& keyCodes, uint8_t* outFlags) {
     std::scoped_lock _l(mLock);
@@ -903,7 +909,9 @@
 
 bool InputReader::setKernelWakeEnabled(int32_t deviceId, bool enabled) {
     std::scoped_lock _l(mLock);
-
+    if (!com::android::input::flags::set_input_device_kernel_wake()){
+        return false;
+    }
     InputDevice* device = findInputDeviceLocked(deviceId);
     if (device) {
         return device->setKernelWakeEnabled(enabled);
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index abe7a5f..4744dd0 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -48,7 +48,7 @@
     inline InputReaderContext* getContext() { return mContext; }
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
-    inline int32_t getGeneration() const { return mGeneration; }
+    inline virtual int32_t getGeneration() const { return mGeneration; }
     inline const std::string getName() const { return mIdentifier.name; }
     inline const std::string getDescriptor() { return mIdentifier.descriptor; }
     inline std::optional<std::string> getBluetoothAddress() const {
@@ -59,7 +59,7 @@
     inline virtual uint32_t getSources() const { return mSources; }
     inline bool hasEventHubDevices() const { return !mDevices.empty(); }
 
-    inline bool isExternal() { return mIsExternal; }
+    inline virtual bool isExternal() { return mIsExternal; }
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
@@ -79,7 +79,7 @@
 
     inline bool isIgnored() { return !getMapperCount() && !mController; }
 
-    inline KeyboardType getKeyboardType() const { return mKeyboardType; }
+    inline virtual KeyboardType getKeyboardType() const { return mKeyboardType; }
 
     bool isEnabled();
 
@@ -124,7 +124,7 @@
     int32_t getMetaState();
     void setKeyboardType(KeyboardType keyboardType);
 
-    void bumpGeneration();
+    virtual void bumpGeneration();
 
     [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when);
 
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 7614a05..1403ca2 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -69,6 +69,8 @@
 
     void toggleCapsLockState(int32_t deviceId) override;
 
+    void resetLockedModifierState() override;
+
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector<int32_t>& keyCodes,
                  uint8_t* outFlags) override;
 
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index ca797dc..8235c90 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -100,9 +100,14 @@
 
 std::list<NotifyArgs> InputMapperUnitTest::process(nsecs_t when, int32_t type, int32_t code,
                                                    int32_t value) {
+    return process(when, when, type, code, value);
+}
+
+std::list<NotifyArgs> InputMapperUnitTest::process(nsecs_t when, nsecs_t readTime, int32_t type,
+                                                   int32_t code, int32_t value) {
     RawEvent event;
     event.when = when;
-    event.readTime = when;
+    event.readTime = readTime;
     event.deviceId = mMapper->getDeviceContext().getEventHubId();
     event.type = type;
     event.code = code;
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index b6c5812..10ef6f1 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -56,6 +56,8 @@
 
     std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
     std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
+    std::list<NotifyArgs> process(nsecs_t when, nsecs_t readTime, int32_t type, int32_t code,
+                                  int32_t value);
 
     InputDeviceIdentifier mIdentifier;
     MockEventHubInterface mMockEventHub;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index ee3b2a2..9d2256f 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -3031,1048 +3031,6 @@
     mapper.assertProcessWasCalled();
 }
 
-// --- KeyboardInputMapperTest ---
-
-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);
-
-    void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
-                             int32_t originalKeyCode, int32_t rotatedKeyCode,
-                             ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID);
-};
-
-/* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the
- * orientation.
- */
-void KeyboardInputMapperTest::prepareDisplay(ui::Rotation orientation) {
-    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID,
-                                 NO_PORT, ViewportType::INTERNAL);
-}
-
-void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper,
-                                                  int32_t originalScanCode, int32_t originalKeyCode,
-                                                  int32_t rotatedKeyCode,
-                                                  ui::LogicalDisplayId displayId) {
-    NotifyKeyArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(originalScanCode, args.scanCode);
-    ASSERT_EQ(rotatedKeyCode, args.keyCode);
-    ASSERT_EQ(displayId, args.displayId);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(originalScanCode, args.scanCode);
-    ASSERT_EQ(rotatedKeyCode, args.keyCode);
-    ASSERT_EQ(displayId, args.displayId);
-}
-
-TEST_F(KeyboardInputMapperTest, GetSources) {
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources());
-}
-
-TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
-    const int32_t USAGE_A = 0x070004;
-    const int32_t USAGE_UNKNOWN = 0x07ffff;
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, POLICY_FLAG_WAKE);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, POLICY_FLAG_WAKE);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    // Initial metastate is AMETA_NONE.
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-
-    // Key down by scan code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
-    ASSERT_EQ(KEY_HOME, args.scanCode);
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Key up by scan code.
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
-    ASSERT_EQ(KEY_HOME, args.scanCode);
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Key down by usage code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, 0, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(AKEYCODE_A, args.keyCode);
-    ASSERT_EQ(0, args.scanCode);
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Key up by usage code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A);
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, 0, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(AKEYCODE_A, args.keyCode);
-    ASSERT_EQ(0, args.scanCode);
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Key down with unknown scan code or usage code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(0, args.keyCode);
-    ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-    ASSERT_EQ(0U, args.policyFlags);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Key up with unknown scan code or usage code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_UNKNOWN, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(0, args.keyCode);
-    ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-    ASSERT_EQ(0U, args.policyFlags);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-}
-
-TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_B, 0, AKEYCODE_B, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    mFakeEventHub->setKeyRemapping(EVENTHUB_ID, {{AKEYCODE_A, AKEYCODE_B}});
-    // Key down by scan code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEYCODE_B, args.keyCode);
-
-    // Key up by scan code.
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEYCODE_B, args.keyCode);
-}
-
-/**
- * Ensure that the readTime is set to the time when the EV_KEY is received.
- */
-TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    NotifyKeyArgs args;
-
-    // Key down
-    process(mapper, ARBITRARY_TIME, /*readTime=*/12, EV_KEY, KEY_HOME, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(12, args.readTime);
-
-    // Key up
-    process(mapper, ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(15, args.readTime);
-}
-
-TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    // Initial metastate is AMETA_NONE.
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-
-    // Metakey down.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState());
-    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled());
-
-    // Key down.
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState());
-
-    // Key up.
-    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, KEY_A, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState());
-
-    // Metakey up.
-    process(mapper, ARBITRARY_TIME + 3, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AMETA_NONE, args.metaState);
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled());
-}
-
-TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    prepareDisplay(ui::ROTATION_90);
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
-            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
-            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
-            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
-            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
-}
-
-TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
-
-    addConfigurationProperty("keyboard.orientationAware", "1");
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    prepareDisplay(ui::ROTATION_0);
-    ASSERT_NO_FATAL_FAILURE(
-            testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
-                                                AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN,
-                                                AKEYCODE_DPAD_DOWN, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT,
-                                                AKEYCODE_DPAD_LEFT, DISPLAY_ID));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_90);
-    ASSERT_NO_FATAL_FAILURE(
-            testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
-                                                AKEYCODE_DPAD_UP, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN,
-                                                AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT,
-                                                AKEYCODE_DPAD_DOWN, DISPLAY_ID));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_180);
-    ASSERT_NO_FATAL_FAILURE(
-            testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
-                                                AKEYCODE_DPAD_LEFT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN,
-                                                AKEYCODE_DPAD_UP, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT,
-                                                AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_270);
-    ASSERT_NO_FATAL_FAILURE(
-            testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
-                                                AKEYCODE_DPAD_DOWN, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN,
-                                                AKEYCODE_DPAD_LEFT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT,
-                                                AKEYCODE_DPAD_UP, DISPLAY_ID));
-
-    // Special case: if orientation changes while key is down, we still emit the same keycode
-    // in the key up as we did in the key down.
-    NotifyKeyArgs args;
-    clearViewports();
-    prepareDisplay(ui::ROTATION_270);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(KEY_UP, args.scanCode);
-    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_180);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(KEY_UP, args.scanCode);
-    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
-}
-
-TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware) {
-    // If the keyboard is not orientation aware,
-    // key events should not be associated with a specific display id
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    NotifyKeyArgs args;
-
-    // Display id should be LogicalDisplayId::INVALID without any display configuration.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId);
-
-    prepareDisplay(ui::ROTATION_0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId);
-}
-
-TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) {
-    // If the keyboard is orientation aware,
-    // key events should be associated with the internal viewport
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
-
-    addConfigurationProperty("keyboard.orientationAware", "1");
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    NotifyKeyArgs args;
-
-    // Display id should be LogicalDisplayId::INVALID without any display configuration.
-    // ^--- already checked by the previous test
-
-    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                 UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DISPLAY_ID, args.displayId);
-
-    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
-    clearViewports();
-    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                 UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(newDisplayId, args.displayId);
-}
-
-TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1);
-    ASSERT_EQ(1, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
-
-    mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 0);
-    ASSERT_EQ(0, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
-}
-
-TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
-    ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y))
-            << "If a mapping is available, the result is equal to the mapping";
-
-    ASSERT_EQ(AKEYCODE_A, mapper.getKeyCodeForKeyLocation(AKEYCODE_A))
-            << "If no mapping is available, the result is the key location";
-}
-
-TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1);
-    ASSERT_EQ(1, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
-
-    mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 0);
-    ASSERT_EQ(0, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
-}
-
-TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
-
-    uint8_t flags[2] = { 0, 0 };
-    ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_A, AKEYCODE_B}, flags));
-    ASSERT_TRUE(flags[0]);
-    ASSERT_FALSE(flags[1]);
-}
-
-TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) {
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    // Initial metastate is AMETA_NONE.
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-
-    // Initialization should have turned all of the lights off.
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-
-    // Toggle caps lock on.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
-
-    // Toggle num lock on.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState());
-
-    // Toggle caps lock off.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper.getMetaState());
-
-    // Toggle scroll lock on.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
-
-    // Toggle num lock off.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
-
-    // Toggle scroll lock off.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-}
-
-TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) {
-    // keyboard 1.
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
-
-    // keyboard 2.
-    const std::string USB2 = "USB2";
-    const std::string DEVICE_NAME2 = "KEYBOARD2";
-    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
-    std::shared_ptr<InputDevice> device2 =
-            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      ftl::Flags<InputDeviceClass>(0));
-
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
-    KeyboardInputMapper& mapper2 =
-            device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
-                                                                mFakePolicy
-                                                                        ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD);
-    std::list<NotifyArgs> unused =
-            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/{});
-    unused += device2->reset(ARBITRARY_TIME);
-
-    // Prepared displays and associated info.
-    constexpr uint8_t hdmi1 = 0;
-    constexpr uint8_t hdmi2 = 1;
-    const std::string SECONDARY_UNIQUE_ID = "local:1";
-
-    mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
-    mFakePolicy->addInputPortAssociation(USB2, hdmi2);
-
-    // No associated display viewport found, should disable the device.
-    unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_FALSE(device2->isEnabled());
-
-    // Prepare second display.
-    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
-    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                 UNIQUE_ID, hdmi1, ViewportType::INTERNAL);
-    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                 SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL);
-    // Default device will reconfigure above, need additional reconfiguration for another device.
-    unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // Device should be enabled after the associated display is found.
-    ASSERT_TRUE(mDevice->isEnabled());
-    ASSERT_TRUE(device2->isEnabled());
-
-    // Test pad key events
-    ASSERT_NO_FATAL_FAILURE(
-            testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
-                                                AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN,
-                                                AKEYCODE_DPAD_DOWN, DISPLAY_ID));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT,
-                                                AKEYCODE_DPAD_LEFT, DISPLAY_ID));
-
-    ASSERT_NO_FATAL_FAILURE(
-            testDPadKeyRotation(mapper2, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, newDisplayId));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
-                                                AKEYCODE_DPAD_RIGHT, newDisplayId));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_DOWN, AKEYCODE_DPAD_DOWN,
-                                                AKEYCODE_DPAD_DOWN, newDisplayId));
-    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_LEFT, AKEYCODE_DPAD_LEFT,
-                                                AKEYCODE_DPAD_LEFT, newDisplayId));
-}
-
-TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) {
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    // Initial metastate is AMETA_NONE.
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-
-    // Initialization should have turned all of the lights off.
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-
-    // Toggle caps lock on.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
-
-    // Toggle num lock on.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState());
-
-    // Toggle scroll lock on.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
-
-    mFakeEventHub->removeDevice(EVENTHUB_ID);
-    mReader->loopOnce();
-
-    // keyboard 2 should default toggle keys.
-    const std::string USB2 = "USB2";
-    const std::string DEVICE_NAME2 = "KEYBOARD2";
-    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
-    std::shared_ptr<InputDevice> device2 =
-            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      ftl::Flags<InputDeviceClass>(0));
-    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
-    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
-    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
-
-    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
-    KeyboardInputMapper& mapper2 =
-            device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
-                                                                mFakePolicy
-                                                                        ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD);
-    std::list<NotifyArgs> unused =
-            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/{});
-    unused += device2->reset(ARBITRARY_TIME);
-
-    ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL));
-    ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_NUML));
-    ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON,
-              mapper2.getMetaState());
-}
-
-TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
-
-    // Suppose we have two mappers. (DPAD + KEYBOARD)
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    // Initial metastate is AMETA_NONE.
-    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
-
-    mReader->toggleCapsLockState(DEVICE_ID);
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
-}
-
-TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
-    // keyboard 1.
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
-    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
-
-    KeyboardInputMapper& mapper1 =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    // keyboard 2.
-    const std::string USB2 = "USB2";
-    const std::string DEVICE_NAME2 = "KEYBOARD2";
-    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
-    std::shared_ptr<InputDevice> device2 =
-            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      ftl::Flags<InputDeviceClass>(0));
-    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
-    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
-    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
-    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
-
-    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
-    KeyboardInputMapper& mapper2 =
-            device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
-                                                                mFakePolicy
-                                                                        ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD);
-    std::list<NotifyArgs> unused =
-            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/{});
-    unused += device2->reset(ARBITRARY_TIME);
-
-    // Initial metastate is AMETA_NONE.
-    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
-
-    // Toggle num lock on and off.
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper2.getMetaState());
-
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
-    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
-
-    // Toggle caps lock on and off.
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper2.getMetaState());
-
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
-    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
-
-    // Toggle scroll lock on and off.
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
-    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper2.getMetaState());
-
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
-    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
-    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
-    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
-}
-
-TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) {
-    const int32_t USAGE_A = 0x070004;
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
-    mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    // Key down by scan code.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
-    ASSERT_EQ(KEY_HOME, args.scanCode);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
-
-    // Disable device, it should synthesize cancellation events for down events.
-    mFakePolicy->addDisabledDevice(DEVICE_ID);
-    configureDevice(InputReaderConfiguration::Change::ENABLED_STATE);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
-    ASSERT_EQ(KEY_HOME, args.scanCode);
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags);
-}
-
-TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    std::list<NotifyArgs> unused =
-            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/{});
-
-    uint32_t generation = mReader->getContext()->getGeneration();
-    mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
-
-    unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
-
-    InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
-    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag,
-              deviceInfo.getKeyboardLayoutInfo()->languageTag);
-    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType,
-              deviceInfo.getKeyboardLayoutInfo()->layoutType);
-    ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
-
-    // Call change layout association with the same values: Generation shouldn't change
-    generation = mReader->getContext()->getGeneration();
-    mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
-    unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                                 InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
-    ASSERT_TRUE(mReader->getContext()->getGeneration() == generation);
-}
-
-TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) {
-    mFakeEventHub->setRawLayoutInfo(EVENTHUB_ID,
-                                    RawLayoutInfo{.languageTag = "en", .layoutType = "extended"});
-
-    // Configuration
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
-
-    ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag);
-    ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
-}
-
-TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE);
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-    NotifyKeyArgs args;
-
-    // Key down
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFT, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags);
-}
-
-TEST_F_WITH_FLAGS(KeyboardInputMapperTest, WakeBehavior_AlphabeticKeyboard,
-                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
-                                                      enable_alphabetic_keyboard_wake))) {
-    // For internal alphabetic devices, keys will trigger wake on key down.
-
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE, 0);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-}
-
-/**
- * When there is more than one KeyboardInputMapper for an InputDevice, each mapper should produce
- * events that use the shared keyboard source across all mappers. This is to ensure that each
- * input device generates key events in a consistent manner, regardless of which mapper produces
- * the event.
- */
-TEST_F(KeyboardInputMapperTest, UsesSharedKeyboardSource) {
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
-
-    // Add a mapper with SOURCE_KEYBOARD
-    KeyboardInputMapper& keyboardMapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
-    ASSERT_NO_FATAL_FAILURE(
-            mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD)));
-    process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
-    ASSERT_NO_FATAL_FAILURE(
-            mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD)));
-
-    // Add a mapper with SOURCE_DPAD
-    KeyboardInputMapper& dpadMapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
-    for (auto* mapper : {&keyboardMapper, &dpadMapper}) {
-        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
-                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD)));
-        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
-                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD)));
-    }
-
-    // Add a mapper with SOURCE_GAMEPAD
-    KeyboardInputMapper& gamepadMapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_GAMEPAD);
-    for (auto* mapper : {&keyboardMapper, &dpadMapper, &gamepadMapper}) {
-        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
-                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD)));
-        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
-                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD)));
-    }
-}
-
-// --- KeyboardInputMapperTest_ExternalAlphabeticDevice ---
-
-class KeyboardInputMapperTest_ExternalAlphabeticDevice : public InputMapperTest {
-protected:
-    void SetUp() override {
-        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
-                               InputDeviceClass::ALPHAKEY | InputDeviceClass::EXTERNAL);
-    }
-};
-
-// --- 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.
-
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE,
-                          POLICY_FLAG_WAKE);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-}
-
-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).
-
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE,
-                          POLICY_FLAG_WAKE);
-
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-}
-
-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);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, POLICY_FLAG_WAKE);
-
-    addConfigurationProperty("keyboard.doNotWakeByDefault", "1");
-    KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
-    NotifyKeyArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_DOWN, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_DOWN, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
-}
-
 // --- TouchInputMapperTest ---
 
 class TouchInputMapperTest : public InputMapperTest {
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 25e2b45..6f7c2e5 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -201,7 +201,9 @@
 
     MOCK_METHOD(uint32_t, getSources, (), (const, override));
     MOCK_METHOD(std::optional<DisplayViewport>, getAssociatedViewport, (), (const));
+    MOCK_METHOD(KeyboardType, getKeyboardType, (), (const, override));
     MOCK_METHOD(bool, isEnabled, (), ());
+    MOCK_METHOD(bool, isExternal, (), (override));
 
     MOCK_METHOD(void, dump, (std::string& dump, const std::string& eventHubDevStr), ());
     MOCK_METHOD(void, addEmptyEventHubDevice, (int32_t eventHubId), ());
@@ -249,8 +251,6 @@
     MOCK_METHOD(int32_t, getMetaState, (), ());
     MOCK_METHOD(void, setKeyboardType, (KeyboardType keyboardType), ());
 
-    MOCK_METHOD(void, bumpGeneration, (), ());
-
     MOCK_METHOD(const PropertyMap&, getConfiguration, (), (const, override));
 
     MOCK_METHOD(NotifyDeviceResetArgs, notifyReset, (nsecs_t when), ());
@@ -260,5 +260,11 @@
     MOCK_METHOD(void, updateLedState, (bool reset), ());
 
     MOCK_METHOD(size_t, getMapperCount, (), ());
+
+    virtual int32_t getGeneration() const override { return mGeneration; }
+    virtual void bumpGeneration() override { mGeneration++; }
+
+private:
+    int32_t mGeneration = 0;
 };
 } // namespace android
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index bcc6062..1dd32c4 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -16,29 +16,82 @@
 
 #include "KeyboardInputMapper.h"
 
-#include <gtest/gtest.h>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
 
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
+#include <ftl/flags.h>
+#include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <ui/LogicalDisplayId.h>
+#include <ui/Rotation.h>
+#include <utils/Errors.h>
+
+#include "EventHub.h"
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
+#include "NotifyArgs.h"
+#include "TestConstants.h"
 #include "TestEventMatchers.h"
 
 #define TAG "KeyboardInputMapper_test"
 
 namespace android {
 
+using namespace ftl::flag_operators;
 using testing::_;
 using testing::AllOf;
+using testing::AnyOf;
 using testing::Args;
 using testing::DoAll;
+using testing::IsEmpty;
 using testing::Return;
+using testing::ReturnArg;
+using testing::SaveArg;
 using testing::SetArgPointee;
 using testing::VariantWith;
 
+namespace {
+
+// Arbitrary display properties.
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+
+DisplayViewport createPrimaryViewport(ui::Rotation orientation) {
+    const bool isRotated =
+            orientation == ui::Rotation::Rotation90 || orientation == ui::Rotation::Rotation270;
+    DisplayViewport v;
+    v.displayId = DISPLAY_ID;
+    v.orientation = orientation;
+    v.logicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.logicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.physicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.physicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.deviceWidth = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.deviceHeight = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.isActive = true;
+    v.uniqueId = "local:1";
+    return v;
+}
+
+} // namespace
+
 /**
  * Unit tests for KeyboardInputMapper.
  */
 class KeyboardInputMapperUnitTest : public InputMapperUnitTest {
 protected:
+    const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
+
     sp<FakeInputReaderPolicy> mFakePolicy;
     const std::unordered_map<int32_t, int32_t> mKeyCodeMap{{KEY_0, AKEYCODE_0},
                                                            {KEY_A, AKEYCODE_A},
@@ -60,9 +113,8 @@
         InputMapperUnitTest::SetUp();
 
         // set key-codes expected in tests
-        for (const auto& [scanCode, outKeycode] : mKeyCodeMap) {
-            EXPECT_CALL(mMockEventHub, mapKey(EVENTHUB_ID, scanCode, _, _, _, _, _))
-                    .WillRepeatedly(DoAll(SetArgPointee<4>(outKeycode), Return(NO_ERROR)));
+        for (const auto& [evdevCode, outKeycode] : mKeyCodeMap) {
+            addKeyByEvdevCode(evdevCode, outKeycode);
         }
 
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
@@ -73,8 +125,79 @@
         mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
                                                          AINPUT_SOURCE_KEYBOARD);
     }
+
+    void addKeyByEvdevCode(int32_t evdevCode, int32_t keyCode, int32_t flags = 0) {
+        EXPECT_CALL(mMockEventHub, mapKey(EVENTHUB_ID, evdevCode, _, _, _, _, _))
+                .WillRepeatedly([=](int32_t, int32_t, int32_t, int32_t metaState,
+                                    int32_t* outKeycode, int32_t* outMetaState,
+                                    uint32_t* outFlags) {
+                    if (outKeycode != nullptr) {
+                        *outKeycode = keyCode;
+                    }
+                    if (outMetaState != nullptr) {
+                        *outMetaState = metaState;
+                    }
+                    if (outFlags != nullptr) {
+                        *outFlags = flags;
+                    }
+                    return NO_ERROR;
+                });
+    }
+
+    void addKeyByUsageCode(int32_t usageCode, int32_t keyCode, int32_t flags = 0) {
+        EXPECT_CALL(mMockEventHub, mapKey(EVENTHUB_ID, _, usageCode, _, _, _, _))
+                .WillRepeatedly([=](int32_t, int32_t, int32_t, int32_t metaState,
+                                    int32_t* outKeycode, int32_t* outMetaState,
+                                    uint32_t* outFlags) {
+                    if (outKeycode != nullptr) {
+                        *outKeycode = keyCode;
+                    }
+                    if (outMetaState != nullptr) {
+                        *outMetaState = metaState;
+                    }
+                    if (outFlags != nullptr) {
+                        *outFlags = flags;
+                    }
+                    return NO_ERROR;
+                });
+    }
+
+    void setDisplayOrientation(ui::Rotation orientation) {
+        EXPECT_CALL((*mDevice), getAssociatedViewport)
+                .WillRepeatedly(Return(createPrimaryViewport(orientation)));
+        std::list<NotifyArgs> args =
+                mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                     InputReaderConfiguration::Change::DISPLAY_INFO);
+        ASSERT_EQ(0u, args.size());
+    }
+
+    NotifyKeyArgs expectSingleKeyArg(const std::list<NotifyArgs>& args) {
+        EXPECT_EQ(1u, args.size());
+        return std::get<NotifyKeyArgs>(args.front());
+    }
+
+    void testDPadKeyRotation(int32_t originalEvdevCode, int32_t originalKeyCode,
+                             int32_t rotatedKeyCode, ui::LogicalDisplayId displayId = DISPLAY_ID) {
+        std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, originalEvdevCode, 1);
+        NotifyKeyArgs args = expectSingleKeyArg(argsList);
+        ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+        ASSERT_EQ(originalEvdevCode, args.scanCode);
+        ASSERT_EQ(rotatedKeyCode, args.keyCode);
+        ASSERT_EQ(displayId, args.displayId);
+
+        argsList = process(ARBITRARY_TIME, EV_KEY, originalEvdevCode, 0);
+        args = expectSingleKeyArg(argsList);
+        ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+        ASSERT_EQ(originalEvdevCode, args.scanCode);
+        ASSERT_EQ(rotatedKeyCode, args.keyCode);
+        ASSERT_EQ(displayId, args.displayId);
+    }
 };
 
+TEST_F(KeyboardInputMapperUnitTest, GetSources) {
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mMapper->getSources());
+}
+
 TEST_F(KeyboardInputMapperUnitTest, KeyPressTimestampRecorded) {
     nsecs_t when = ARBITRARY_TIME;
     std::vector<int32_t> keyCodes{KEY_0, KEY_A, KEY_LEFTCTRL, KEY_RIGHTALT, KEY_LEFTSHIFT};
@@ -109,4 +232,962 @@
                                                              WithScanCode(KEY_0)))));
 }
 
+TEST_F(KeyboardInputMapperUnitTest, Process_SimpleKeyPress) {
+    const int32_t USAGE_A = 0x070004;
+    addKeyByEvdevCode(KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    addKeyByUsageCode(USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
+
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mMapper->getMetaState());
+
+    // Key down by evdev code.
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    NotifyKeyArgs args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up by evdev code.
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key down by usage code.
+    argsList = process(ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_A);
+    argsList += process(ARBITRARY_TIME, EV_KEY, 0, 1);
+    args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEYCODE_A, args.keyCode);
+    ASSERT_EQ(0, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up by usage code.
+    argsList = process(ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_A);
+    argsList += process(ARBITRARY_TIME + 1, EV_KEY, 0, 0);
+    args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_A, args.keyCode);
+    ASSERT_EQ(0, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Process_UnknownKey) {
+    const int32_t USAGE_UNKNOWN = 0x07ffff;
+    EXPECT_CALL(mMockEventHub, mapKey(EVENTHUB_ID, KEY_UNKNOWN, USAGE_UNKNOWN, _, _, _, _))
+            .WillRepeatedly(Return(NAME_NOT_FOUND));
+
+    // Key down with unknown scan code or usage code.
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+    argsList += process(ARBITRARY_TIME, EV_KEY, KEY_UNKNOWN, 1);
+    NotifyKeyArgs args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(0, args.keyCode);
+    ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(0U, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up with unknown scan code or usage code.
+    argsList = process(ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+    argsList += process(ARBITRARY_TIME + 1, EV_KEY, KEY_UNKNOWN, 0);
+    args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(0, args.keyCode);
+    ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(0U, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+/**
+ * Ensure that the readTime is set to the time when the EV_KEY is received.
+ */
+TEST_F(KeyboardInputMapperUnitTest, Process_SendsReadTime) {
+    addKeyByEvdevCode(KEY_HOME, AKEYCODE_HOME);
+
+    // Key down
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, /*readTime=*/12, EV_KEY, KEY_HOME, 1);
+    ASSERT_EQ(12, expectSingleKeyArg(argsList).readTime);
+
+    // Key up
+    argsList = process(ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1);
+    ASSERT_EQ(15, expectSingleKeyArg(argsList).readTime);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Process_ShouldUpdateMetaState) {
+    addKeyByEvdevCode(KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT);
+    addKeyByEvdevCode(KEY_A, AKEYCODE_A);
+
+    EXPECT_CALL(mMockInputReaderContext, updateGlobalMetaState()).Times(2);
+
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mMapper->getMetaState());
+
+    // Metakey down.
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_LEFTSHIFT, 1);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, expectSingleKeyArg(argsList).metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mMapper->getMetaState());
+
+    // Key down.
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_A, 1);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, expectSingleKeyArg(argsList).metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mMapper->getMetaState());
+
+    // Key up.
+    argsList = process(ARBITRARY_TIME + 2, EV_KEY, KEY_A, 0);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, expectSingleKeyArg(argsList).metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mMapper->getMetaState());
+
+    // Metakey up.
+    argsList = process(ARBITRARY_TIME + 3, EV_KEY, KEY_LEFTSHIFT, 0);
+    ASSERT_EQ(AMETA_NONE, expectSingleKeyArg(argsList).metaState);
+    ASSERT_EQ(AMETA_NONE, mMapper->getMetaState());
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
+    addKeyByEvdevCode(KEY_UP, AKEYCODE_DPAD_UP);
+    addKeyByEvdevCode(KEY_RIGHT, AKEYCODE_DPAD_RIGHT);
+    addKeyByEvdevCode(KEY_DOWN, AKEYCODE_DPAD_DOWN);
+    addKeyByEvdevCode(KEY_LEFT, AKEYCODE_DPAD_LEFT);
+
+    setDisplayOrientation(ui::Rotation::Rotation90);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(
+            testDPadKeyRotation(KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Process_WhenOrientationAware_ShouldRotateDPad) {
+    addKeyByEvdevCode(KEY_UP, AKEYCODE_DPAD_UP);
+    addKeyByEvdevCode(KEY_RIGHT, AKEYCODE_DPAD_RIGHT);
+    addKeyByEvdevCode(KEY_DOWN, AKEYCODE_DPAD_DOWN);
+    addKeyByEvdevCode(KEY_LEFT, AKEYCODE_DPAD_LEFT);
+
+    mPropertyMap.addProperty("keyboard.orientationAware", "1");
+    mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                     AINPUT_SOURCE_KEYBOARD);
+    setDisplayOrientation(ui::ROTATION_0);
+
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(
+            testDPadKeyRotation(KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+
+    setDisplayOrientation(ui::ROTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN));
+
+    setDisplayOrientation(ui::ROTATION_180);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(
+            testDPadKeyRotation(KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT));
+
+    setDisplayOrientation(ui::ROTATION_270);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(
+            testDPadKeyRotation(KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_UP));
+
+    // Special case: if orientation changes while key is down, we still emit the same keycode
+    // in the key up as we did in the key down.
+    setDisplayOrientation(ui::ROTATION_270);
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    NotifyKeyArgs args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(KEY_UP, args.scanCode);
+    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+
+    setDisplayOrientation(ui::ROTATION_180);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(KEY_UP, args.scanCode);
+    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, DisplayIdConfigurationChange_NotOrientationAware) {
+    // If the keyboard is not orientation aware,
+    // key events should not be associated with a specific display id
+    addKeyByEvdevCode(KEY_UP, AKEYCODE_DPAD_UP);
+
+    // Display id should be LogicalDisplayId::INVALID without any display configuration.
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    ASSERT_GT(argsList.size(), 0u);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    ASSERT_GT(argsList.size(), 0u);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, std::get<NotifyKeyArgs>(argsList.front()).displayId);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, DisplayIdConfigurationChange_OrientationAware) {
+    // If the keyboard is orientation aware,
+    // key events should be associated with the internal viewport
+    addKeyByEvdevCode(KEY_UP, AKEYCODE_DPAD_UP);
+
+    mPropertyMap.addProperty("keyboard.orientationAware", "1");
+    mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                     AINPUT_SOURCE_KEYBOARD);
+
+    // Display id should be LogicalDisplayId::INVALID without any display configuration.
+    // ^--- already checked by the previous test
+
+    setDisplayOrientation(ui::ROTATION_0);
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    ASSERT_GT(argsList.size(), 0u);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    ASSERT_GT(argsList.size(), 0u);
+    ASSERT_EQ(DISPLAY_ID, std::get<NotifyKeyArgs>(argsList.front()).displayId);
+
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
+    DisplayViewport newViewport = createPrimaryViewport(ui::ROTATION_0);
+    newViewport.displayId = newDisplayId;
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(newViewport));
+    argsList = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                    InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_EQ(0u, argsList.size());
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    ASSERT_GT(argsList.size(), 0u);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    ASSERT_GT(argsList.size(), 0u);
+    ASSERT_EQ(newDisplayId, std::get<NotifyKeyArgs>(argsList.front()).displayId);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, GetKeyCodeState) {
+    EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, AKEYCODE_A))
+            .WillRepeatedly(Return(AKEY_STATE_DOWN));
+    ASSERT_EQ(AKEY_STATE_DOWN, mMapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+    EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, AKEYCODE_A))
+            .WillRepeatedly(Return(AKEY_STATE_UP));
+    ASSERT_EQ(AKEY_STATE_UP, mMapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+}
+
+TEST_F(KeyboardInputMapperUnitTest, GetKeyCodeForKeyLocation) {
+    EXPECT_CALL(mMockEventHub, getKeyCodeForKeyLocation(EVENTHUB_ID, _))
+            .WillRepeatedly(ReturnArg<1>());
+    EXPECT_CALL(mMockEventHub, getKeyCodeForKeyLocation(EVENTHUB_ID, AKEYCODE_Y))
+            .WillRepeatedly(Return(AKEYCODE_Z));
+    ASSERT_EQ(AKEYCODE_Z, mMapper->getKeyCodeForKeyLocation(AKEYCODE_Y))
+            << "If a mapping is available, the result is equal to the mapping";
+
+    ASSERT_EQ(AKEYCODE_A, mMapper->getKeyCodeForKeyLocation(AKEYCODE_A))
+            << "If no mapping is available, the result is the key location";
+}
+
+TEST_F(KeyboardInputMapperUnitTest, GetScanCodeState) {
+    EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, KEY_A))
+            .WillRepeatedly(Return(AKEY_STATE_DOWN));
+    ASSERT_EQ(AKEY_STATE_DOWN, mMapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+    EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, KEY_A))
+            .WillRepeatedly(Return(AKEY_STATE_UP));
+    ASSERT_EQ(AKEY_STATE_UP, mMapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Process_LockedKeysShouldToggleMetaStateAndLeds) {
+    EXPECT_CALL(mMockEventHub,
+                hasLed(EVENTHUB_ID, AnyOf(LED_CAPSL, LED_NUML, LED_SCROLLL /*NOTYPO*/)))
+            .WillRepeatedly(Return(true));
+    bool capsLockLed = true;    // Initially on
+    bool numLockLed = false;    // Initially off
+    bool scrollLockLed = false; // Initially off
+    EXPECT_CALL(mMockEventHub, setLedState(EVENTHUB_ID, LED_CAPSL, _))
+            .WillRepeatedly(SaveArg<2>(&capsLockLed));
+    EXPECT_CALL(mMockEventHub, setLedState(EVENTHUB_ID, LED_NUML, _))
+            .WillRepeatedly(SaveArg<2>(&numLockLed));
+    EXPECT_CALL(mMockEventHub, setLedState(EVENTHUB_ID, LED_SCROLLL /*NOTYPO*/, _))
+            .WillRepeatedly(SaveArg<2>(&scrollLockLed));
+    addKeyByEvdevCode(KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK);
+    addKeyByEvdevCode(KEY_NUMLOCK, AKEYCODE_NUM_LOCK);
+    addKeyByEvdevCode(KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK);
+
+    // In real operation, mappers pass new LED states to InputReader (via the context), which then
+    // calls back to the mappers to apply that state. Mimic the same thing here with mocks.
+    int32_t ledMetaState;
+    EXPECT_CALL(mMockInputReaderContext, updateLedMetaState(_))
+            .WillRepeatedly([&](int32_t newState) {
+                ledMetaState = newState;
+                mMapper->updateLedState(false);
+            });
+    EXPECT_CALL(mMockInputReaderContext, getLedMetaState())
+            .WillRepeatedly(testing::ReturnPointee(&ledMetaState));
+
+    ASSERT_THAT(mMapper->reset(ARBITRARY_TIME), IsEmpty());
+
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mMapper->getMetaState());
+
+    // Initialization should have turned all of the lights off.
+    ASSERT_FALSE(capsLockLed);
+    ASSERT_FALSE(numLockLed);
+    ASSERT_FALSE(scrollLockLed);
+
+    // Toggle caps lock on.
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_TRUE(capsLockLed);
+    ASSERT_FALSE(numLockLed);
+    ASSERT_FALSE(scrollLockLed);
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mMapper->getMetaState());
+
+    // Toggle num lock on.
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_TRUE(capsLockLed);
+    ASSERT_TRUE(numLockLed);
+    ASSERT_FALSE(scrollLockLed);
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mMapper->getMetaState());
+
+    // Toggle caps lock off.
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_FALSE(capsLockLed);
+    ASSERT_TRUE(numLockLed);
+    ASSERT_FALSE(scrollLockLed);
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mMapper->getMetaState());
+
+    // Toggle scroll lock on.
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_FALSE(capsLockLed);
+    ASSERT_TRUE(numLockLed);
+    ASSERT_TRUE(scrollLockLed);
+    ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mMapper->getMetaState());
+
+    // Toggle num lock off.
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_FALSE(capsLockLed);
+    ASSERT_FALSE(numLockLed);
+    ASSERT_TRUE(scrollLockLed);
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mMapper->getMetaState());
+
+    // Toggle scroll lock off.
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_FALSE(capsLockLed);
+    ASSERT_FALSE(numLockLed);
+    ASSERT_FALSE(scrollLockLed);
+    ASSERT_EQ(AMETA_NONE, mMapper->getMetaState());
+}
+
+TEST_F(KeyboardInputMapperUnitTest, DisablingDeviceResetsPressedKeys) {
+    const int32_t USAGE_A = 0x070004;
+    addKeyByEvdevCode(KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    addKeyByUsageCode(USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
+
+    // Key down by scan code.
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    NotifyKeyArgs args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+
+    // Disable device, it should synthesize cancellation events for down events.
+    mReaderConfiguration.disabledDevices.insert(DEVICE_ID);
+    argsList = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                    InputReaderConfiguration::Change::ENABLED_STATE);
+    argsList += mMapper->reset(ARBITRARY_TIME);
+    args = expectSingleKeyArg(argsList);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Configure_AssignKeyboardLayoutInfo) {
+    std::list<NotifyArgs> unused =
+            mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, /*changes=*/{});
+
+    int32_t generation = mDevice->getGeneration();
+    mReaderConfiguration.keyboardLayoutAssociations.insert(
+            {mIdentifier.location, DEVICE_KEYBOARD_LAYOUT_INFO});
+
+    unused += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                   InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
+
+    InputDeviceInfo deviceInfo;
+    mMapper->populateDeviceInfo(deviceInfo);
+    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag,
+              deviceInfo.getKeyboardLayoutInfo()->languageTag);
+    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType,
+              deviceInfo.getKeyboardLayoutInfo()->layoutType);
+    ASSERT_GT(mDevice->getGeneration(), generation);
+
+    // Call change layout association with the same values: Generation shouldn't change
+    generation = mDevice->getGeneration();
+    unused += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                   InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
+    ASSERT_EQ(mDevice->getGeneration(), generation);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, LayoutInfoCorrectlyMapped) {
+    EXPECT_CALL(mMockEventHub, getRawLayoutInfo(EVENTHUB_ID))
+            .WillRepeatedly(Return(RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}));
+
+    // Configuration
+    std::list<NotifyArgs> unused =
+            mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, /*changes=*/{});
+
+    InputDeviceInfo deviceInfo;
+    mMapper->populateDeviceInfo(deviceInfo);
+    ASSERT_EQ("en", deviceInfo.getKeyboardLayoutInfo()->languageTag);
+    ASSERT_EQ("extended", deviceInfo.getKeyboardLayoutInfo()->layoutType);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, Process_GestureEventToSetFlagKeepTouchMode) {
+    addKeyByEvdevCode(KEY_LEFT, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE);
+
+    // Key down
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_LEFT, 1);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE,
+              expectSingleKeyArg(argsList).flags);
+}
+
+TEST_F_WITH_FLAGS(KeyboardInputMapperUnitTest, WakeBehavior_AlphabeticKeyboard,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      enable_alphabetic_keyboard_wake))) {
+    // For internal alphabetic devices, keys will trigger wake on key down.
+
+    addKeyByEvdevCode(KEY_A, AKEYCODE_A);
+    addKeyByEvdevCode(KEY_HOME, AKEYCODE_HOME);
+    addKeyByEvdevCode(KEY_PLAYPAUSE, AKEYCODE_MEDIA_PLAY_PAUSE);
+
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_A, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_A, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_PLAYPAUSE, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+}
+
+// --- KeyboardInputMapperTest ---
+
+// TODO(b/283812079): convert the tests for this class, which use multiple mappers each, to use
+//  InputMapperUnitTest.
+class KeyboardInputMapperTest : public InputMapperTest {
+protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::ALPHAKEY);
+    }
+    const std::string UNIQUE_ID = "local:0";
+
+    void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
+                             int32_t originalKeyCode, int32_t rotatedKeyCode,
+                             ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID);
+};
+
+void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper,
+                                                  int32_t originalScanCode, int32_t originalKeyCode,
+                                                  int32_t rotatedKeyCode,
+                                                  ui::LogicalDisplayId displayId) {
+    NotifyKeyArgs args;
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(originalScanCode, args.scanCode);
+    ASSERT_EQ(rotatedKeyCode, args.keyCode);
+    ASSERT_EQ(displayId, args.displayId);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(originalScanCode, args.scanCode);
+    ASSERT_EQ(rotatedKeyCode, args.keyCode);
+    ASSERT_EQ(displayId, args.displayId);
+}
+
+TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) {
+    // keyboard 1.
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
+    // keyboard 2.
+    const std::string USB2 = "USB2";
+    const std::string DEVICE_NAME2 = "KEYBOARD2";
+    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
+    std::shared_ptr<InputDevice> device2 =
+            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
+                      ftl::Flags<InputDeviceClass>(0));
+
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+
+    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
+    KeyboardInputMapper& mapper2 =
+            device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+                                                                mFakePolicy
+                                                                        ->getReaderConfiguration(),
+                                                                AINPUT_SOURCE_KEYBOARD);
+    std::list<NotifyArgs> unused =
+            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                    /*changes=*/{});
+    unused += device2->reset(ARBITRARY_TIME);
+
+    // Prepared displays and associated info.
+    constexpr uint8_t hdmi1 = 0;
+    constexpr uint8_t hdmi2 = 1;
+    const std::string SECONDARY_UNIQUE_ID = "local:1";
+
+    mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
+    mFakePolicy->addInputPortAssociation(USB2, hdmi2);
+
+    // No associated display viewport found, should disable the device.
+    unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_FALSE(device2->isEnabled());
+
+    // Prepare second display.
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
+    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                 UNIQUE_ID, hdmi1, ViewportType::INTERNAL);
+    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                 SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL);
+    // Default device will reconfigure above, need additional reconfiguration for another device.
+    unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    // Device should be enabled after the associated display is found.
+    ASSERT_TRUE(mDevice->isEnabled());
+    ASSERT_TRUE(device2->isEnabled());
+
+    // Test pad key events
+    ASSERT_NO_FATAL_FAILURE(
+            testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
+                                                AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN,
+                                                AKEYCODE_DPAD_DOWN, DISPLAY_ID));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT,
+                                                AKEYCODE_DPAD_LEFT, DISPLAY_ID));
+
+    ASSERT_NO_FATAL_FAILURE(
+            testDPadKeyRotation(mapper2, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, newDisplayId));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
+                                                AKEYCODE_DPAD_RIGHT, newDisplayId));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_DOWN, AKEYCODE_DPAD_DOWN,
+                                                AKEYCODE_DPAD_DOWN, newDisplayId));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_LEFT, AKEYCODE_DPAD_LEFT,
+                                                AKEYCODE_DPAD_LEFT, newDisplayId));
+}
+
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) {
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
+
+    // Initialization should have turned all of the lights off.
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+
+    // Toggle caps lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
+
+    // Toggle num lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState());
+
+    // Toggle scroll lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
+
+    mFakeEventHub->removeDevice(EVENTHUB_ID);
+    mReader->loopOnce();
+
+    // keyboard 2 should default toggle keys.
+    const std::string USB2 = "USB2";
+    const std::string DEVICE_NAME2 = "KEYBOARD2";
+    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
+    std::shared_ptr<InputDevice> device2 =
+            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
+                      ftl::Flags<InputDeviceClass>(0));
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
+    KeyboardInputMapper& mapper2 =
+            device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+                                                                mFakePolicy
+                                                                        ->getReaderConfiguration(),
+                                                                AINPUT_SOURCE_KEYBOARD);
+    std::list<NotifyArgs> unused =
+            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                    /*changes=*/{});
+    unused += device2->reset(ARBITRARY_TIME);
+
+    ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL));
+    ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_NUML));
+    ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON,
+              mapper2.getMetaState());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    // Suppose we have two mappers. (DPAD + KEYBOARD)
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
+
+    mReader->toggleCapsLockState(DEVICE_ID);
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_ResetLockedModifierState) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
+
+    // Toggle caps lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+
+    // Toggle num lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+
+    // Toggle scroll lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
+
+    mReader->resetLockedModifierState();
+    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
+    // keyboard 1.
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper1 =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+
+    // keyboard 2.
+    const std::string USB2 = "USB2";
+    const std::string DEVICE_NAME2 = "KEYBOARD2";
+    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
+    std::shared_ptr<InputDevice> device2 =
+            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
+                      ftl::Flags<InputDeviceClass>(0));
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
+    KeyboardInputMapper& mapper2 =
+            device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+                                                                mFakePolicy
+                                                                        ->getReaderConfiguration(),
+                                                                AINPUT_SOURCE_KEYBOARD);
+    std::list<NotifyArgs> unused =
+            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                    /*changes=*/{});
+    unused += device2->reset(ARBITRARY_TIME);
+
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle num lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle caps lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle scroll lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+}
+
+/**
+ * When there is more than one KeyboardInputMapper for an InputDevice, each mapper should produce
+ * events that use the shared keyboard source across all mappers. This is to ensure that each
+ * input device generates key events in a consistent manner, regardless of which mapper produces
+ * the event.
+ */
+TEST_F(KeyboardInputMapperTest, UsesSharedKeyboardSource) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+
+    // Add a mapper with SOURCE_KEYBOARD
+    KeyboardInputMapper& keyboardMapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+
+    process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD)));
+    process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD)));
+
+    // Add a mapper with SOURCE_DPAD
+    KeyboardInputMapper& dpadMapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
+    for (auto* mapper : {&keyboardMapper, &dpadMapper}) {
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD)));
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD)));
+    }
+
+    // Add a mapper with SOURCE_GAMEPAD
+    KeyboardInputMapper& gamepadMapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_GAMEPAD);
+    for (auto* mapper : {&keyboardMapper, &dpadMapper, &gamepadMapper}) {
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD)));
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD)));
+    }
+}
+
+// --- KeyboardInputMapperTest_ExternalAlphabeticDevice ---
+
+class KeyboardInputMapperTest_ExternalAlphabeticDevice : public KeyboardInputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD));
+        ON_CALL((*mDevice), getKeyboardType).WillByDefault(Return(KeyboardType::ALPHABETIC));
+        ON_CALL((*mDevice), isExternal).WillByDefault(Return(true));
+        EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
+                .WillRepeatedly(Return(InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY |
+                                       InputDeviceClass::EXTERNAL));
+        mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                         AINPUT_SOURCE_KEYBOARD);
+    }
+};
+
+// --- KeyboardInputMapperTest_ExternalNonAlphabeticDevice ---
+
+class KeyboardInputMapperTest_ExternalNonAlphabeticDevice : public KeyboardInputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD));
+        ON_CALL((*mDevice), getKeyboardType).WillByDefault(Return(KeyboardType::NON_ALPHABETIC));
+        ON_CALL((*mDevice), isExternal).WillByDefault(Return(true));
+        EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
+                .WillRepeatedly(Return(InputDeviceClass::KEYBOARD | InputDeviceClass::EXTERNAL));
+        mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                         AINPUT_SOURCE_KEYBOARD);
+    }
+};
+
+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.
+
+    addKeyByEvdevCode(KEY_HOME, AKEYCODE_HOME);
+    addKeyByEvdevCode(KEY_PLAY, AKEYCODE_MEDIA_PLAY);
+    addKeyByEvdevCode(KEY_PLAYPAUSE, AKEYCODE_MEDIA_PLAY_PAUSE, POLICY_FLAG_WAKE);
+
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_PLAY, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_PLAY, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_PLAYPAUSE, 0);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+}
+
+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).
+
+    addKeyByEvdevCode(KEY_PLAY, AKEYCODE_MEDIA_PLAY);
+    addKeyByEvdevCode(KEY_PLAYPAUSE, AKEYCODE_MEDIA_PLAY_PAUSE, POLICY_FLAG_WAKE);
+
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_PLAY, 1);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_PLAY, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_PLAYPAUSE, 0);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+}
+
+TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, DoNotWakeByDefaultBehavior) {
+    // Tv Remote key's wake behavior is prescribed by the keylayout file.
+
+    addKeyByEvdevCode(KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    addKeyByEvdevCode(KEY_DOWN, AKEYCODE_DPAD_DOWN);
+    addKeyByEvdevCode(KEY_PLAY, AKEYCODE_MEDIA_PLAY, POLICY_FLAG_WAKE);
+
+    mPropertyMap.addProperty("keyboard.doNotWakeByDefault", "1");
+    mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                     AINPUT_SOURCE_KEYBOARD);
+
+    std::list<NotifyArgs> argsList = process(ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_DOWN, 1);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_DOWN, 0);
+    ASSERT_EQ(uint32_t(0), expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME, EV_KEY, KEY_PLAY, 1);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+
+    argsList = process(ARBITRARY_TIME + 1, EV_KEY, KEY_PLAY, 0);
+    ASSERT_EQ(POLICY_FLAG_WAKE, expectSingleKeyArg(argsList).policyFlags);
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 64f3c27..6be922d 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -75,6 +75,8 @@
 
     void toggleCapsLockState(int32_t deviceId) { reader->toggleCapsLockState(deviceId); }
 
+    void resetLockedModifierState() { reader->resetLockedModifierState(); }
+
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector<int32_t>& keyCodes,
                  uint8_t* outFlags) {
         return reader->hasKeys(deviceId, sourceMask, keyCodes, outFlags);
@@ -226,6 +228,7 @@
                                            fdp->ConsumeIntegral<int32_t>());
                 },
                 [&]() -> void { reader->toggleCapsLockState(fdp->ConsumeIntegral<int32_t>()); },
+                [&]() -> void { reader->resetLockedModifierState(); },
                 [&]() -> void {
                     size_t count = fdp->ConsumeIntegralInRange<size_t>(1, 1024);
                     std::vector<uint8_t> outFlags(count);
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index 40fd097..0ba1909 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -168,6 +168,11 @@
                                           "closeSessionChannel"));
 }
 
+HalResult<aidl::android::hardware::power::SupportInfo> PowerHalController::getSupportInfo() {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return CACHE_SUPPORT(6, processHalResult(handle->getSupportInfo(), "getSupportInfo"));
+}
+
 } // namespace power
 
 } // namespace android
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index bd6685c..068c23f 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -18,6 +18,7 @@
 #include <aidl/android/hardware/power/Boost.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SupportInfo.h>
 #include <powermanager/HalResult.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
@@ -73,6 +74,11 @@
     return HalResult<void>::unsupported();
 }
 
+HalResult<Aidl::SupportInfo> EmptyHalWrapper::getSupportInfo() {
+    ALOGV("Skipped getSupportInfo because %s", getUnsupportedMessage());
+    return HalResult<Aidl::SupportInfo>::unsupported();
+}
+
 const char* EmptyHalWrapper::getUnsupportedMessage() {
     return "Power HAL is not supported";
 }
@@ -280,6 +286,12 @@
     return HalResult<void>::fromStatus(mHandle->closeSessionChannel(tgid, uid));
 }
 
+HalResult<Aidl::SupportInfo> AidlHalWrapper::getSupportInfo() {
+    Aidl::SupportInfo support;
+    auto result = mHandle->getSupportInfo(&support);
+    return HalResult<Aidl::SupportInfo>::fromStatus(result, std::move(support));
+}
+
 const char* AidlHalWrapper::getUnsupportedMessage() {
     return "Power HAL doesn't support it";
 }
diff --git a/services/surfaceflinger/ActivePictureUpdater.cpp b/services/surfaceflinger/ActivePictureUpdater.cpp
new file mode 100644
index 0000000..210e948
--- /dev/null
+++ b/services/surfaceflinger/ActivePictureUpdater.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ActivePictureUpdater.h"
+
+#include <algorithm>
+
+#include "Layer.h"
+#include "LayerFE.h"
+
+namespace android {
+
+void ActivePictureUpdater::onLayerComposed(const Layer& layer, const LayerFE& layerFE,
+                                           const CompositionResult& result) {
+    if (result.wasPictureProfileCommitted) {
+        gui::ActivePicture picture;
+        picture.layerId = int32_t(layer.sequence);
+        picture.ownerUid = int32_t(layer.getOwnerUid());
+        // TODO(b/337330263): Why does LayerFE coming from SF have a null composition state?
+        if (layerFE.getCompositionState()) {
+            picture.pictureProfileId = layerFE.getCompositionState()->pictureProfileHandle.getId();
+        } else {
+            picture.pictureProfileId = result.pictureProfileHandle.getId();
+        }
+        mNewActivePictures.push_back(picture);
+    }
+}
+
+bool ActivePictureUpdater::updateAndHasChanged() {
+    bool hasChanged = true;
+    if (mNewActivePictures.size() == mOldActivePictures.size()) {
+        auto compare = [](const gui::ActivePicture& lhs, const gui::ActivePicture& rhs) -> int {
+            if (lhs.layerId == rhs.layerId) {
+                return lhs.pictureProfileId < rhs.pictureProfileId;
+            }
+            return lhs.layerId < rhs.layerId;
+        };
+        std::sort(mNewActivePictures.begin(), mNewActivePictures.end(), compare);
+        if (std::equal(mNewActivePictures.begin(), mNewActivePictures.end(),
+                       mOldActivePictures.begin())) {
+            hasChanged = false;
+        }
+    }
+    std::swap(mOldActivePictures, mNewActivePictures);
+    mNewActivePictures.resize(0);
+    return hasChanged;
+}
+
+const std::vector<gui::ActivePicture>& ActivePictureUpdater::getActivePictures() const {
+    return mOldActivePictures;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/ActivePictureUpdater.h b/services/surfaceflinger/ActivePictureUpdater.h
new file mode 100644
index 0000000..20779bb
--- /dev/null
+++ b/services/surfaceflinger/ActivePictureUpdater.h
@@ -0,0 +1,47 @@
+/*
+ * 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 <vector>
+
+#include <android/gui/ActivePicture.h>
+
+namespace android {
+
+class Layer;
+class LayerFE;
+struct CompositionResult;
+
+// Keeps track of active pictures - layers that are undergoing picture processing.
+class ActivePictureUpdater {
+public:
+    // Called for each visible layer when SurfaceFlinger finishes composing.
+    void onLayerComposed(const Layer& layer, const LayerFE& layerFE,
+                         const CompositionResult& result);
+
+    // Update internals and return whether the set of active pictures have changed.
+    bool updateAndHasChanged();
+
+    // The current set of active pictures.
+    const std::vector<gui::ActivePicture>& getActivePictures() const;
+
+private:
+    std::vector<gui::ActivePicture> mOldActivePictures;
+    std::vector<gui::ActivePicture> mNewActivePictures;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 8a667ae..3f3d2c6 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -199,6 +199,7 @@
     name: "libsurfaceflinger_sources",
     srcs: [
         ":libsurfaceflinger_backend_sources",
+        "ActivePictureUpdater.cpp",
         "BackgroundExecutor.cpp",
         "Client.cpp",
         "ClientCache.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a5e9dde..cda4edc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -161,6 +161,9 @@
     // Checks if the buffer's release fence has been set
     virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0;
 
+    // Indicates that the picture profile request was applied to this layer.
+    virtual void onPictureProfileCommitted() = 0;
+
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index c7ff704..272fa3e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -58,6 +58,7 @@
     MOCK_CONST_METHOD0(hasRoundedCorners, bool());
     MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*());
     MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*());
+    MOCK_METHOD0(onPictureProfileCommitted, void());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 98b6666..f9ed92d 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -830,11 +830,13 @@
         for (int i = 0; i < getMaxLayerPictureProfiles() && !layersWithProfiles.empty();
              layersWithProfiles.pop(), ++i) {
             layersWithProfiles.top()->commitPictureProfileToCompositionState();
+            layersWithProfiles.top()->getLayerFE().onPictureProfileCommitted();
         }
         // No layer-specific picture processing, so apply the highest priority picture profile to
         // the entire display.
     } else if (!layersWithProfiles.empty()) {
         editState().pictureProfileHandle = layersWithProfiles.top()->getPictureProfileHandle();
+        layersWithProfiles.top()->getLayerFE().onPictureProfileCommitted();
     }
 }
 
@@ -895,6 +897,12 @@
                                          })) {
         editState().earliestPresentTime = frameTargetPtrOpt->get()->earliestPresentTime();
         editState().expectedPresentTime = frameTargetPtrOpt->get()->expectedPresentTime().ns();
+        const auto debugPresentDelay = frameTargetPtrOpt->get()->debugPresentDelay();
+        if (debugPresentDelay) {
+            SFTRACE_FORMAT_INSTANT("DEBUG delaying presentation by %.2fms",
+                                   debugPresentDelay->ns() / 1e6f);
+            editState().expectedPresentTime += debugPresentDelay->ns();
+        }
     }
     editState().frameInterval = refreshArgs.frameInterval;
     editState().powerCallback = refreshArgs.powerCallback;
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index f6d9a1a..a040c88 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -26,6 +26,7 @@
 #include <ui/FloatRect.h>
 #include <ui/HdrRenderTypeUtils.h>
 #include <cstdint>
+#include <limits>
 #include "system/graphics-base-v1.0.h"
 
 #include <com_android_graphics_libgui_flags.h>
@@ -398,11 +399,22 @@
     // For hdr content, treat the white point as the display brightness - HDR content should not be
     // boosted or dimmed.
     // If the layer explicitly requests to disable dimming, then don't dim either.
-    if (hdrRenderType == HdrRenderType::GENERIC_HDR ||
-        getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
-        getOutput().getState().displayBrightnessNits == 0.f || !layerFEState->dimmingEnabled) {
+    if (getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
+        getOutput().getState().displayBrightnessNits <= 0.f || !layerFEState->dimmingEnabled) {
         state.dimmingRatio = 1.f;
         state.whitePointNits = getOutput().getState().displayBrightnessNits;
+    } else if (hdrRenderType == HdrRenderType::GENERIC_HDR) {
+        float deviceHeadroom = getOutput().getState().displayBrightnessNits /
+                getOutput().getState().sdrWhitePointNits;
+        float idealizedMaxHeadroom = deviceHeadroom;
+
+        if (FlagManager::getInstance().begone_bright_hlg()) {
+            idealizedMaxHeadroom =
+                    std::min(idealizedMaxHeadroom, getIdealizedMaxHeadroom(state.dataspace));
+        }
+
+        state.dimmingRatio = std::min(idealizedMaxHeadroom / deviceHeadroom, 1.0f);
+        state.whitePointNits = getOutput().getState().displayBrightnessNits * state.dimmingRatio;
     } else {
         float layerBrightnessNits = getOutput().getState().sdrWhitePointNits;
         // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular
@@ -432,7 +444,7 @@
     }
     const auto* layerState = getLayerFE().getCompositionState();
     if (layerState) {
-        editState().pictureProfileHandle = getLayerFE().getCompositionState()->pictureProfileHandle;
+        editState().pictureProfileHandle = layerState->pictureProfileHandle;
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 99e68eb..442b603 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -5117,6 +5117,11 @@
     // Sets display picture profile to the highest priority layer's profile
     EXPECT_CALL(mHwComposer, setDisplayPictureProfileHandle(_, Eq(profileForLayer2)));
 
+    // Marks only the highest priority layer as committed
+    EXPECT_CALL(*layer1.layerFE, onPictureProfileCommitted).Times(0);
+    EXPECT_CALL(*layer2.layerFE, onPictureProfileCommitted);
+    EXPECT_CALL(*layer3.layerFE, onPictureProfileCommitted).Times(0);
+
     mOutput->editState().isEnabled = true;
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
@@ -5172,6 +5177,11 @@
     EXPECT_CALL(*layer2.outputLayer, commitPictureProfileToCompositionState);
     EXPECT_CALL(*layer3.outputLayer, commitPictureProfileToCompositionState);
 
+    // Marks only the highest priority layers as committed
+    EXPECT_CALL(*layer1.layerFE, onPictureProfileCommitted).Times(0);
+    EXPECT_CALL(*layer2.layerFE, onPictureProfileCommitted);
+    EXPECT_CALL(*layer3.layerFE, onPictureProfileCommitted);
+
     // No display picture profile is sent
     EXPECT_CALL(mHwComposer, setDisplayPictureProfileHandle).Times(0);
 
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 7569c1b..4d9a9ca 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -261,20 +261,25 @@
     }
     snapshot.isVisible = visible;
 
-    // TODO(b/238781169) we are ignoring this compat for now, since we will have
-    // to remove any optimization based on visibility.
+    if (FlagManager::getInstance().skip_invisible_windows_in_input()) {
+        snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    } else {
+        // TODO(b/238781169) we are ignoring this compat for now, since we will have
+        // to remove any optimization based on visibility.
 
-    // For compatibility reasons we let layers which can receive input
-    // receive input before they have actually submitted a buffer. Because
-    // of this we use canReceiveInput instead of isVisible to check the
-    // policy-visibility, ignoring the buffer state. However for layers with
-    // hasInputInfo()==false we can use the real visibility state.
-    // We are just using these layers for occlusion detection in
-    // InputDispatcher, and obviously if they aren't visible they can't occlude
-    // anything.
-    const bool visibleForInput =
-            snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
-    snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput);
+        // For compatibility reasons we let layers which can receive input
+        // receive input before they have actually submitted a buffer. Because
+        // of this we use canReceiveInput instead of isVisible to check the
+        // policy-visibility, ignoring the buffer state. However for layers with
+        // hasInputInfo()==false we can use the real visibility state.
+        // We are just using these layers for occlusion detection in
+        // InputDispatcher, and obviously if they aren't visible they can't occlude
+        // anything.
+        const bool visibleForInput =
+                snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
+        snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE,
+                                          !visibleForInput);
+    }
     LLOGV(snapshot.sequence, "updating visibility %s %s", visible ? "true" : "false",
           snapshot.getDebugString().c_str());
 }
@@ -1260,6 +1265,10 @@
     for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
         LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
         if (!snapshot.hasInputInfo()) continue;
+        if (FlagManager::getInstance().skip_invisible_windows_in_input() &&
+            snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) {
+            continue;
+        }
         visitor(snapshot);
     }
 }
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 231b40b..fea7671 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -342,8 +342,15 @@
     caster.shadow = state;
 }
 
-CompositionResult&& LayerFE::stealCompositionResult() {
-    return std::move(mCompositionResult);
+void LayerFE::onPictureProfileCommitted() {
+    mCompositionResult.wasPictureProfileCommitted = true;
+    mCompositionResult.pictureProfileHandle = mSnapshot->pictureProfileHandle;
+}
+
+CompositionResult LayerFE::stealCompositionResult() {
+    CompositionResult result;
+    std::swap(mCompositionResult, result);
+    return result;
 }
 
 const char* LayerFE::getDebugName() const {
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 5081e10..9483aeb 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -18,6 +18,9 @@
 
 #include <android/gui/CachingHint.h>
 #include <gui/LayerMetadata.h>
+#include <ui/LayerStack.h>
+#include <ui/PictureProfileHandle.h>
+
 #include "FrontEnd/LayerSnapshot.h"
 #include "compositionengine/LayerFE.h"
 #include "compositionengine/LayerFECompositionState.h"
@@ -29,6 +32,10 @@
 
 struct CompositionResult {
     sp<Fence> lastClientCompositionFence = nullptr;
+    bool wasPictureProfileCommitted = false;
+    // TODO(b/337330263): Why does LayerFE coming from SF have a null composition state?
+    // It would be better not to duplicate this information
+    PictureProfileHandle pictureProfileHandle = PictureProfileHandle::NONE;
 };
 
 class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE {
@@ -47,10 +54,11 @@
     const gui::LayerMetadata* getRelativeMetadata() const override;
     std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
-    CompositionResult&& stealCompositionResult();
+    CompositionResult stealCompositionResult();
     ftl::Future<FenceResult> createReleaseFenceFuture() override;
     void setReleaseFence(const FenceResult& releaseFence) override;
     LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
+    void onPictureProfileCommitted() override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 84fa139..e4069dd 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -770,17 +770,13 @@
             const bool inPrimaryPhysicalRange =
                     policy->primaryRanges.physical.includes(modePtr->getPeakFps());
             const bool inPrimaryRenderRange = policy->primaryRanges.render.includes(fps);
-            if (!mIsVrrDevice.load() &&
-                ((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
+            if (((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
                  !inPrimaryRenderRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
                    layer.vote == LayerVoteType::ExplicitExact))) {
                 // Only focused layers with ExplicitDefault frame rate settings are allowed to score
                 // refresh rates outside the primary range.
-                ALOGV("%s ignores %s (primaryRangeIsSingleRate). Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
-                      to_string(activeMode).c_str());
                 continue;
             }
 
@@ -904,8 +900,7 @@
                                    to_string(descending.front().frameRateMode.fps).c_str());
             return {descending, kNoSignals};
         } else {
-            ALOGV("%s (primaryRangeIsSingleRate)",
-                  to_string(ranking.front().frameRateMode.fps).c_str());
+            ALOGV("primaryRangeIsSingleRate");
             SFTRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
                                    to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
@@ -1041,14 +1036,12 @@
 
         // Layers with ExplicitExactOrMultiple expect touch boost
         if (globalSignals.touch && hasExplicitExactOrMultiple) {
-            ALOGV("%s: Skipping for touch (input signal): uid=%d", __func__, uid);
             continue;
         }
 
         // Mirrors getRankedFrameRates. If there is no ExplicitDefault, expect touch boost and
         // skip frame rate override.
         if (hasHighHint && !hasExplicitDefault) {
-            ALOGV("%s: Skipping for touch (HighHint): uid=%d", __func__, uid);
             continue;
         }
 
@@ -1072,9 +1065,6 @@
                 constexpr bool isSeamlessSwitch = true;
                 const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch);
                 score += layer->weight * layerScore;
-                ALOGV("%s: %s gives %s fps score of %.4f", __func__,
-                      formatLayerInfo(*layer, layer->weight).c_str(), to_string(fps).c_str(),
-                      layerScore);
             }
         }
 
@@ -1329,8 +1319,6 @@
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
     mActiveModeOpt = FrameRateMode{activeModeOpt->get()->getPeakFps(),
                                    ftl::as_non_null(activeModeOpt->get())};
-    mIsVrrDevice = FlagManager::getInstance().vrr_config() &&
-            activeModeOpt->get()->getVrrConfig().has_value();
 
     const auto sortedModes = sortByRefreshRate(mDisplayModes);
     mMinRefreshRateModeIt = sortedModes.front();
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 8c587a9..2875650 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -203,12 +203,16 @@
 
 void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId,
                               TimePoint expectedVsyncTime) {
+    const auto debugPresentDelay = mDebugPresentDelay.load();
+    mDebugPresentDelay.store(std::nullopt);
+
     const FrameTargeter::BeginFrameArgs beginFrameArgs =
             {.frameBeginTime = SchedulerClock::now(),
              .vsyncId = vsyncId,
              .expectedVsyncTime = expectedVsyncTime,
              .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration,
-             .hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration};
+             .hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration,
+             .debugPresentTimeDelay = debugPresentDelay};
 
     ftl::NonNull<const Display*> pacesetterPtr = pacesetterPtrLocked();
     pacesetterPtr->targeterPtr->beginFrame(beginFrameArgs, *pacesetterPtr->schedulePtr);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 8340880..61c68a9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -337,6 +337,8 @@
     // recovery should begin.
     void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids);
 
+    void setDebugPresentDelay(TimePoint delay) { mDebugPresentDelay = delay; }
+
 private:
     friend class TestableScheduler;
 
@@ -602,6 +604,8 @@
 
     FrameRateOverrideMappings mFrameRateOverrideMappings;
     SmallAreaDetectionAllowMappings mSmallAreaDetectionAllowMappings;
+
+    std::atomic<std::optional<TimePoint>> mDebugPresentDelay;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index 2185bb0..813d4de 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -53,6 +53,8 @@
 
     TimePoint expectedPresentTime() const { return mExpectedPresentTime; }
 
+    std::optional<TimePoint> debugPresentDelay() const { return mDebugPresentTimeDelay; }
+
     std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; }
 
     // Equivalent to `expectedSignaledPresentFence` unless running N VSYNCs ahead.
@@ -84,6 +86,7 @@
     TimePoint mFrameBeginTime;
     TimePoint mExpectedPresentTime;
     std::optional<TimePoint> mEarliestPresentTime;
+    std::optional<TimePoint> mDebugPresentTimeDelay;
 
     TracedOrdinal<bool> mFramePending;
     TracedOrdinal<bool> mFrameMissed;
@@ -135,6 +138,7 @@
         TimePoint expectedVsyncTime;
         Duration sfWorkDuration;
         Duration hwcMinWorkDuration;
+        std::optional<TimePoint> debugPresentTimeDelay; // used to introduce jank for testing
     };
 
     void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 3ee1e54..4f16130 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -86,6 +86,7 @@
                                IsFencePendingFuncPtr isFencePendingFuncPtr) {
     mVsyncId = args.vsyncId;
     mFrameBeginTime = args.frameBeginTime;
+    mDebugPresentTimeDelay = args.debugPresentTimeDelay;
 
     // The `expectedVsyncTime`, which was predicted when this frame was scheduled, is normally in
     // the future relative to `frameBeginTime`, but may not be for delayed frames. Adjust
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9854174..1c61b11 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -126,6 +126,7 @@
 #include <gui/SchedulingPolicy.h>
 #include <gui/SyncScreenCaptureListener.h>
 #include <ui/DisplayIdentification.h>
+#include "ActivePictureUpdater.h"
 #include "BackgroundExecutor.h"
 #include "Client.h"
 #include "ClientCache.h"
@@ -372,6 +373,7 @@
 const String16 sRotateSurfaceFlinger("android.permission.ROTATE_SURFACE_FLINGER");
 const String16 sReadFramebuffer("android.permission.READ_FRAME_BUFFER");
 const String16 sControlDisplayBrightness("android.permission.CONTROL_DISPLAY_BRIGHTNESS");
+const String16 sObservePictureProfiles("android.permission.OBSERVE_PICTURE_PROFILES");
 const String16 sDump("android.permission.DUMP");
 const String16 sCaptureBlackoutContent("android.permission.CAPTURE_BLACKOUT_CONTENT");
 const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW");
@@ -1420,8 +1422,6 @@
     return future.get();
 }
 
-// TODO: b/241285876 - Restore thread safety analysis once mStateLock below is unconditional.
-[[clang::no_thread_safety_analysis]]
 void SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
     SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
@@ -1437,8 +1437,6 @@
     if (const auto oldResolution =
                 mDisplayModeController.getActiveMode(displayId).modePtr->getResolution();
         oldResolution != activeMode.modePtr->getResolution()) {
-        ConditionalLock lock(mStateLock, !FlagManager::getInstance().connected_display());
-
         auto& state = mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId));
         // We need to generate new sequenceId in order to recreate the display (and this
         // way the framebuffer).
@@ -1842,6 +1840,24 @@
     }));
 }
 
+status_t SurfaceFlinger::getMaxLayerPictureProfiles(const sp<IBinder>& displayToken,
+                                                    int32_t* outMaxProfiles) {
+    const char* const whence = __func__;
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+        const ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
+        if (index < 0) {
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
+            return 0;
+        }
+        const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
+        return state.maxLayerPictureProfiles > 0 ? state.maxLayerPictureProfiles
+                : state.hasPictureProcessing     ? 1
+                                                 : 0;
+    });
+    *outMaxProfiles = future.get();
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::overrideHdrTypes(const sp<IBinder>& displayToken,
                                           const std::vector<ui::Hdr>& hdrTypes) {
     Mutex::Autolock lock(mStateLock);
@@ -2252,12 +2268,10 @@
         return;
     }
 
-    if (FlagManager::getInstance().hotplug2()) {
-        // TODO(b/311403559): use enum type instead of int
-        const auto errorCode = static_cast<int32_t>(event);
-        ALOGD("%s: Hotplug error %d for hwcDisplayId %" PRIu64, __func__, errorCode, hwcDisplayId);
-        mScheduler->dispatchHotplugError(errorCode);
-    }
+    // TODO(b/311403559): use enum type instead of int
+    const auto errorCode = static_cast<int32_t>(event);
+    ALOGD("%s: Hotplug error %d for hwcDisplayId %" PRIu64, __func__, errorCode, hwcDisplayId);
+    mScheduler->dispatchHotplugError(errorCode);
 }
 
 void SurfaceFlinger::onComposerHalVsyncPeriodTimingChanged(
@@ -2558,7 +2572,7 @@
     }
 
     {
-        SFTRACE_NAME("LLM:commitChanges");
+        SFTRACE_NAME("LayerLifecycleManager:commitChanges");
         mLayerLifecycleManager.commitChanges();
     }
 
@@ -2599,7 +2613,7 @@
     }
 
     {
-        ConditionalLock lock(mStateLock, FlagManager::getInstance().connected_display());
+        Mutex::Autolock lock(mStateLock);
 
         for (const auto [displayId, _] : frameTargets) {
             if (mDisplayModeController.isModeSetPending(displayId)) {
@@ -2702,13 +2716,6 @@
         mScheduler->chooseRefreshRateForContent(&mLayerHierarchyBuilder.getHierarchy(),
                                                 updateAttachedChoreographer);
 
-        if (FlagManager::getInstance().connected_display()) {
-            initiateDisplayModeChanges();
-        }
-    }
-
-    if (!FlagManager::getInstance().connected_display()) {
-        ftl::FakeGuard guard(mStateLock);
         initiateDisplayModeChanges();
     }
 
@@ -2864,6 +2871,9 @@
         if (compositionResult.lastClientCompositionFence) {
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
         }
+        if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+            mActivePictureUpdater.onLayerComposed(*layer, *layerFE, compositionResult);
+        }
     }
 
     SFTRACE_NAME("postComposition");
@@ -3159,30 +3169,6 @@
         layer->releasePendingBuffer(presentTime.ns());
     }
 
-    std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
-            hdrInfoListeners;
-    bool haveNewListeners = false;
-    {
-        Mutex::Autolock lock(mStateLock);
-        if (mFpsReporter) {
-            mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
-        }
-
-        if (mTunnelModeEnabledReporter) {
-            mTunnelModeEnabledReporter->updateTunnelModeStatus();
-        }
-        hdrInfoListeners.reserve(mHdrLayerInfoListeners.size());
-        for (const auto& [displayId, reporter] : mHdrLayerInfoListeners) {
-            if (reporter && reporter->hasListeners()) {
-                if (const auto display = getDisplayDeviceLocked(displayId)) {
-                    hdrInfoListeners.emplace_back(display->getCompositionDisplay(), reporter);
-                }
-            }
-        }
-        haveNewListeners = mAddingHDRLayerInfoListener; // grab this with state lock
-        mAddingHDRLayerInfoListener = false;
-    }
-
     for (const auto& layerEvent : mLayerEvents) {
         auto result =
                 stats::stats_write(stats::SURFACE_CONTROL_EVENT,
@@ -3193,10 +3179,40 @@
             ALOGW("Failed to report layer event with error: %d", result);
         }
     }
-
     mLayerEvents.clear();
 
-    if (haveNewListeners || mHdrLayerInfoChanged) {
+    std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
+            hdrInfoListeners;
+    bool haveNewHdrInfoListeners = false;
+    sp<gui::IActivePictureListener> activePictureListener;
+    bool haveNewActivePictureListener = false;
+    {
+        Mutex::Autolock lock(mStateLock);
+        if (mFpsReporter) {
+            mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
+        }
+
+        if (mTunnelModeEnabledReporter) {
+            mTunnelModeEnabledReporter->updateTunnelModeStatus();
+        }
+
+        hdrInfoListeners.reserve(mHdrLayerInfoListeners.size());
+        for (const auto& [displayId, reporter] : mHdrLayerInfoListeners) {
+            if (reporter && reporter->hasListeners()) {
+                if (const auto display = getDisplayDeviceLocked(displayId)) {
+                    hdrInfoListeners.emplace_back(display->getCompositionDisplay(), reporter);
+                }
+            }
+        }
+        haveNewHdrInfoListeners = mAddingHDRLayerInfoListener; // grab this with state lock
+        mAddingHDRLayerInfoListener = false;
+
+        activePictureListener = mActivePictureListener;
+        haveNewActivePictureListener = mHaveNewActivePictureListener;
+        mHaveNewActivePictureListener = false;
+    }
+
+    if (haveNewHdrInfoListeners || mHdrLayerInfoChanged) {
         for (auto& [compositionDisplay, listener] : hdrInfoListeners) {
             HdrLayerInfoReporter::HdrLayerInfo info;
             int32_t maxArea = 0;
@@ -3214,7 +3230,15 @@
                                             snapshot.desiredHdrSdrRatio < 1.f
                                             ? std::numeric_limits<float>::infinity()
                                             : snapshot.desiredHdrSdrRatio;
-                                    info.mergeDesiredRatio(desiredHdrSdrRatio);
+
+                                    float desiredRatio = desiredHdrSdrRatio;
+                                    if (FlagManager::getInstance().begone_bright_hlg() &&
+                                        desiredHdrSdrRatio ==
+                                                std::numeric_limits<float>::infinity()) {
+                                        desiredRatio = getIdealizedMaxHeadroom(snapshot.dataspace);
+                                    }
+
+                                    info.mergeDesiredRatio(desiredRatio);
                                     info.numberOfHdrLayers++;
                                     const auto displayFrame = outputLayer->getState().displayFrame;
                                     const int32_t area =
@@ -3246,9 +3270,19 @@
             listener->dispatchHdrLayerInfo(info);
         }
     }
-
     mHdrLayerInfoChanged = false;
 
+    if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+        // Track, update and notify changes to active pictures - layers that are undergoing picture
+        // processing
+        if (mActivePictureUpdater.updateAndHasChanged() || haveNewActivePictureListener) {
+            if (activePictureListener) {
+                activePictureListener->onActivePicturesChanged(
+                        mActivePictureUpdater.getActivePictures());
+            }
+        }
+    }
+
     mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
     mTransactionCallbackInvoker.clearCompletedTransactions();
 
@@ -3494,10 +3528,8 @@
                         processHotplugConnect(displayId, hwcDisplayId, std::move(*info),
                                               displayString.c_str());
                 if (!activeModeIdOpt) {
-                    if (FlagManager::getInstance().hotplug2()) {
-                        mScheduler->dispatchHotplugError(
-                                static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
-                    }
+                    mScheduler->dispatchHotplugError(
+                            static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
                     getHwComposer().disconnectDisplay(displayId);
                     continue;
                 }
@@ -6263,7 +6295,7 @@
     }
     // Numbers from 1000 to 1045 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1045) {
+    if (code >= 1000 && code <= 1046) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -6796,6 +6828,15 @@
                 }
                 return err;
             }
+            // Introduce jank to HWC
+            case 1046: {
+                int32_t jankDelayMs = 0;
+                if (data.readInt32(&jankDelayMs) != NO_ERROR) {
+                    return BAD_VALUE;
+                }
+                mScheduler->setDebugPresentDelay(TimePoint::fromNs(ms2ns(jankDelayMs)));
+                return NO_ERROR;
+            }
         }
     }
     return err;
@@ -8079,6 +8120,14 @@
     }));
 }
 
+void SurfaceFlinger::setActivePictureListener(const sp<gui::IActivePictureListener>& listener) {
+    if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+        Mutex::Autolock lock(mStateLock);
+        mActivePictureListener = listener;
+        mHaveNewActivePictureListener = listener != nullptr;
+    }
+}
+
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
         BufferData& bufferData, const char* layerName, uint64_t transactionId) {
     if (bufferData.buffer &&
@@ -8736,6 +8785,16 @@
     return binder::Status::ok();
 }
 
+binder::Status SurfaceComposerAIDL::getMaxLayerPictureProfiles(const sp<IBinder>& display,
+                                                               int32_t* outMaxProfiles) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
+    mFlinger->getMaxLayerPictureProfiles(display, outMaxProfiles);
+    return binder::Status::ok();
+}
+
 binder::Status SurfaceComposerAIDL::captureDisplay(
         const DisplayCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
     mFlinger->captureDisplay(args, captureListener);
@@ -9014,6 +9073,15 @@
     return binderStatusFromStatusT(status);
 }
 
+binder::Status SurfaceComposerAIDL::setActivePictureListener(
+        const sp<gui::IActivePictureListener>& listener) {
+    status_t status = checkObservePictureProfilesPermission();
+    if (status == OK) {
+        mFlinger->setActivePictureListener(listener);
+    }
+    return binderStatusFromStatusT(status);
+}
+
 binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) {
     status_t status = checkAccessPermission();
     if (status == OK) {
@@ -9269,6 +9337,17 @@
     return OK;
 }
 
+status_t SurfaceComposerAIDL::checkObservePictureProfilesPermission() {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(sObservePictureProfiles, pid, uid)) {
+        ALOGE("Permission Denial: can't manage picture profiles pid=%d, uid=%d", pid, uid);
+        return PERMISSION_DENIED;
+    }
+    return OK;
+}
+
 void SurfaceFlinger::forceFutureUpdate(int delayInMs) {
     static_cast<void>(mScheduler->scheduleDelayed([&]() { scheduleRepaint(); }, ms2ns(delayInMs)));
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7e9d5b8..211f374 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -24,9 +24,11 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/thread_annotations.h>
+#include <android/gui/ActivePicture.h>
 #include <android/gui/BnSurfaceComposer.h>
 #include <android/gui/DisplayStatInfo.h>
 #include <android/gui/DisplayState.h>
+#include <android/gui/IActivePictureListener.h>
 #include <android/gui/IJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
 #include <common/trace.h>
@@ -67,6 +69,8 @@
 #include <ui/FenceResult.h>
 
 #include <common/FlagManager.h>
+#include "ActivePictureUpdater.h"
+#include "BackgroundExecutor.h"
 #include "Display/DisplayModeController.h"
 #include "Display/PhysicalDisplay.h"
 #include "DisplayDevice.h"
@@ -93,6 +97,7 @@
 #include "TransactionState.h"
 #include "Utils/OnceFuture.h"
 
+#include <algorithm>
 #include <atomic>
 #include <cstdint>
 #include <functional>
@@ -591,6 +596,7 @@
     status_t getHdrOutputConversionSupport(bool* outSupport) const;
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
     void setGameContentType(const sp<IBinder>& displayToken, bool on);
+    status_t getMaxLayerPictureProfiles(const sp<IBinder>& displayToken, int32_t* outMaxProfiles);
     void setPowerMode(const sp<IBinder>& displayToken, int mode);
     status_t overrideHdrTypes(const sp<IBinder>& displayToken,
                               const std::vector<ui::Hdr>& hdrTypes);
@@ -660,6 +666,8 @@
 
     void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
 
+    void setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
     // IBinder::DeathRecipient overrides:
     void binderDied(const wp<IBinder>& who) override;
 
@@ -1377,6 +1385,10 @@
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
 
+    sp<gui::IActivePictureListener> mActivePictureListener GUARDED_BY(mStateLock);
+    bool mHaveNewActivePictureListener GUARDED_BY(mStateLock);
+    ActivePictureUpdater mActivePictureUpdater GUARDED_BY(kMainThreadContext);
+
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
     // Must only be accessed on the main thread.
@@ -1522,6 +1534,8 @@
     binder::Status getHdrOutputConversionSupport(bool* outSupport) override;
     binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
     binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
+    binder::Status getMaxLayerPictureProfiles(const sp<IBinder>& display,
+                                              int32_t* outMaxProfiles) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
                                   const sp<IScreenCaptureListener>&) override;
     binder::Status captureDisplayById(int64_t, const CaptureArgs&,
@@ -1610,12 +1624,15 @@
     binder::Status flushJankData(int32_t layerId) override;
     binder::Status removeJankListener(int32_t layerId, const sp<gui::IJankListener>& listener,
                                       int64_t afterVsync) override;
+    binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+    binder::Status clearActivePictureListener();
 
 private:
     static const constexpr bool kUsePermissionCache = true;
     status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache);
     status_t checkControlDisplayBrightnessPermission();
     status_t checkReadFrameBufferPermission();
+    status_t checkObservePictureProfilesPermission();
     static void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info,
                                               gui::DynamicDisplayInfo*& outInfo);
 
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 12616e3..a1b53ee 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -119,10 +119,10 @@
     DUMP_READ_ONLY_FLAG(adpf_fmq_sf);
     DUMP_READ_ONLY_FLAG(connected_display);
     DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+    DUMP_READ_ONLY_FLAG(stable_edid_ids);
     DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
     DUMP_READ_ONLY_FLAG(misc1);
     DUMP_READ_ONLY_FLAG(vrr_config);
-    DUMP_READ_ONLY_FLAG(hotplug2);
     DUMP_READ_ONLY_FLAG(hdcp_level_hal);
     DUMP_READ_ONLY_FLAG(multithreaded_present);
     DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
@@ -159,6 +159,8 @@
     DUMP_READ_ONLY_FLAG(display_config_error_hal);
     DUMP_READ_ONLY_FLAG(connected_display_hdr);
     DUMP_READ_ONLY_FLAG(deprecate_frame_tracker);
+    DUMP_READ_ONLY_FLAG(skip_invisible_windows_in_input);
+    DUMP_READ_ONLY_FLAG(begone_bright_hlg);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -227,10 +229,10 @@
 FLAG_MANAGER_READ_ONLY_FLAG(adpf_fmq_sf, "")
 FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(stable_edid_ids, "debug.sf.stable_edid_ids")
 FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
 FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
-FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
 FLAG_MANAGER_READ_ONLY_FLAG(hdcp_level_hal, "")
 FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
 FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
@@ -266,6 +268,8 @@
 FLAG_MANAGER_READ_ONLY_FLAG(display_config_error_hal, "");
 FLAG_MANAGER_READ_ONLY_FLAG(connected_display_hdr, "");
 FLAG_MANAGER_READ_ONLY_FLAG(deprecate_frame_tracker, "");
+FLAG_MANAGER_READ_ONLY_FLAG(skip_invisible_windows_in_input, "");
+FLAG_MANAGER_READ_ONLY_FLAG(begone_bright_hlg, "debug.sf.begone_bright_hlg");
 
 /// 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 f5bea72..c1d88ce 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -58,9 +58,9 @@
     bool connected_display() const;
     bool frame_rate_category_mrr() const;
     bool enable_small_area_detection() const;
+    bool stable_edid_ids() const;
     bool misc1() const;
     bool vrr_config() const;
-    bool hotplug2() const;
     bool hdcp_level_hal() const;
     bool multithreaded_present() const;
     bool add_sf_skipped_frames_to_trace() const;
@@ -97,6 +97,8 @@
     bool display_config_error_hal() const;
     bool connected_display_hdr() const;
     bool deprecate_frame_tracker() const;
+    bool skip_invisible_windows_in_input() const;
+    bool begone_bright_hlg() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 014c736..1188435 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -35,6 +35,14 @@
 } # arr_surfacecontrol_setframerate_api
 
 flag {
+  name: "begone_bright_hlg"
+  namespace: "core_graphics"
+  description: "Caps HLG brightness relative to SDR"
+  bug: "362510107"
+  is_fixed_read_only: true
+} # begone_bright_hlg
+
+flag {
   name: "ce_fence_promise"
   namespace: "window_surfaces"
   description: "Moves logic for buffer release fences into LayerFE"
@@ -196,6 +204,25 @@
  } # single_hop_screenshot
 
 flag {
+  name: "skip_invisible_windows_in_input"
+  namespace: "window_surfaces"
+  description: "Only send visible windows to input list"
+  bug: "305254099"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # skip_invisible_windows_in_input
+
+flag {
+  name: "stable_edid_ids"
+  namespace: "core_graphics"
+  description: "Guard use of the new stable EDID-based display IDs system."
+  bug: "352320847"
+  is_fixed_read_only: true
+} # stable_edid_ids
+
+flag {
   name: "true_hdr_screenshots"
   namespace: "core_graphics"
   description: "Enables screenshotting display content in HDR, sans tone mapping"
diff --git a/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp b/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp
new file mode 100644
index 0000000..b926d2f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android/gui/ActivePicture.h>
+#include <android/gui/IActivePictureListener.h>
+#include <compositionengine/mock/CompositionEngine.h>
+#include <mock/DisplayHardware/MockComposer.h>
+#include <mock/MockLayer.h>
+#include <renderengine/mock/RenderEngine.h>
+
+#include "ActivePictureUpdater.h"
+#include "LayerFE.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+using android::compositionengine::LayerFECompositionState;
+using android::gui::ActivePicture;
+using android::gui::IActivePictureListener;
+using android::mock::MockLayer;
+using surfaceflinger::frontend::LayerSnapshot;
+using testing::_;
+using testing::NiceMock;
+using testing::Return;
+
+class TestableLayerFE : public LayerFE {
+public:
+    TestableLayerFE() : LayerFE("TestableLayerFE"), snapshot(*(new LayerSnapshot)) {
+        mSnapshot = std::unique_ptr<LayerSnapshot>(&snapshot);
+    }
+
+    LayerSnapshot& snapshot;
+};
+
+class ActivePictureUpdaterTest : public testing::Test {
+protected:
+    SurfaceFlinger* flinger() {
+        if (!mFlingerSetup) {
+            mFlinger.setupMockScheduler();
+            mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+            mFlinger.setupRenderEngine(std::make_unique<renderengine::mock::RenderEngine>());
+            mFlingerSetup = true;
+        }
+        return mFlinger.flinger();
+    }
+
+private:
+    TestableSurfaceFlinger mFlinger;
+    bool mFlingerSetup = false;
+};
+
+// Hack to workaround initializer lists not working for parcelables because parcelables inherit from
+// Parcelable, which has a virtual destructor.
+auto UnorderedElementsAre(std::initializer_list<std::tuple<int32_t, int32_t, int64_t>> tuples) {
+    std::vector<ActivePicture> activePictures;
+    for (auto tuple : tuples) {
+        ActivePicture ap;
+        ap.layerId = std::get<0>(tuple);
+        ap.ownerUid = std::get<1>(tuple);
+        ap.pictureProfileId = std::get<2>(tuple);
+        activePictures.push_back(ap);
+    }
+    return testing::UnorderedElementsAreArray(activePictures);
+}
+
+// Parcelables don't define this for matchers, which is unfortunate
+void PrintTo(const ActivePicture& activePicture, std::ostream* os) {
+    *os << activePicture.toString();
+}
+
+TEST_F(ActivePictureUpdaterTest, notCalledWithNoProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenLayerStartsUsingProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, notCalledWhenLayerContinuesUsingProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenLayerStopsUsingProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenLayerChangesProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 2}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, notCalledWhenUncommittedLayerChangesProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(20)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenDifferentLayerUsesSameProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(20)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 1}, {200, 20, 2}}));
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 2}, {200, 20, 1}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenSameUidUsesSameProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 1}, {200, 10, 2}}));
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 2}, {200, 10, 1}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenNewLayerUsesSameProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 1}, {200, 10, 1}}));
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
index 5413bae..72d1351 100644
--- a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 #include <gtest/gtest.h>
 #include <condition_variable>
 
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 0dfbd61..08e4265 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -166,6 +166,7 @@
                 a.presentTime == b.presentTime;
     }
 
+    NO_THREAD_SAFETY_ANALYSIS
     const std::map<int64_t, TimelineItem>& getPredictions() const {
         return mTokenManager->mPredictions;
     }
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 6aec743..8c53eef 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1550,6 +1550,9 @@
 }
 
 TEST_F(LayerSnapshotTest, NonVisibleLayerWithInput) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              skip_invisible_windows_in_input,
+                      false);
     LayerHierarchyTestBase::createRootLayer(3);
     setColor(3, {-1._hf, -1._hf, -1._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -1575,6 +1578,39 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, NonVisibleLayerWithInputShouldNotBeIncluded) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              skip_invisible_windows_in_input,
+                      true);
+    LayerHierarchyTestBase::createRootLayer(3);
+    setColor(3, {-1._hf, -1._hf, -1._hf});
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+    transactions.back().states.front().layerId = 3;
+    transactions.back().states.front().state.windowInfoHandle = sp<gui::WindowInfoHandle>::make();
+    auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+    inputInfo->token = sp<BBinder>::make();
+    hideLayer(3);
+    mLifecycleManager.applyTransactions(transactions);
+
+    update(mSnapshotBuilder);
+
+    bool foundInputLayer = false;
+    mSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (snapshot.uniqueSequence == 3) {
+            EXPECT_TRUE(
+                    snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE));
+            EXPECT_FALSE(snapshot.isVisible);
+            foundInputLayer = true;
+        }
+    });
+    EXPECT_FALSE(foundInputLayer);
+}
+
 TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) {
     std::vector<uint32_t> visitedUniqueSequences;
     mSnapshotBuilder.forEachSnapshot(
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index b5be8db..80b2b8d 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -308,42 +308,6 @@
                     << " category=" << ftl::enum_string(testCase.frameRateCategory);
         }
     }
-
-    template <class T>
-    std::vector<LayerRequirement> createLayers(const std::initializer_list<T>& surfaceVotes) {
-        std::vector<LayerRequirement> layers;
-        for (auto surfaceVote : surfaceVotes) {
-            ALOGI("**** %s: Adding layers for %s: (desiredFrameRate=%s, voteType=%s), "
-                  "(frameRateCategory=%s)",
-                  __func__, surfaceVote.name.c_str(),
-                  to_string(surfaceVote.desiredFrameRate).c_str(),
-                  ftl::enum_string(surfaceVote.voteType).c_str(),
-                  ftl::enum_string(surfaceVote.frameRateCategory).c_str());
-
-            if (surfaceVote.desiredFrameRate.isValid()) {
-                std::stringstream ss;
-                ss << surfaceVote.name << " (" << surfaceVote.weight << "): ExplicitDefault ("
-                   << to_string(surfaceVote.desiredFrameRate) << ")";
-                LayerRequirement layer = {.name = ss.str(),
-                                          .vote = surfaceVote.voteType,
-                                          .desiredRefreshRate = surfaceVote.desiredFrameRate,
-                                          .weight = surfaceVote.weight};
-                layers.push_back(layer);
-            }
-
-            if (surfaceVote.frameRateCategory != FrameRateCategory::Default) {
-                std::stringstream ss;
-                ss << surfaceVote.name << " (" << surfaceVote.weight << "): ExplicitCategory ("
-                   << ftl::enum_string(surfaceVote.frameRateCategory) << ")";
-                LayerRequirement layer = {.name = ss.str(),
-                                          .vote = LayerVoteType::ExplicitCategory,
-                                          .frameRateCategory = surfaceVote.frameRateCategory,
-                                          .weight = surfaceVote.weight};
-                layers.push_back(layer);
-            }
-        }
-        return layers;
-    }
 };
 
 RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -1812,98 +1776,6 @@
             selector);
 }
 
-TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multiSurface_arr) {
-    if (GetParam() != Config::FrameRateOverride::Enabled) {
-        return;
-    }
-
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-
-    auto selector = createSelector(kVrrMode_120, kModeId120);
-
-    // Switch the policy to be more like an ARR device (primary range is a single rate).
-    constexpr FpsRange k120_120Hz = {120_Hz, 120_Hz};
-    constexpr FpsRange k0_120Hz = {0_Hz, 120_Hz};
-    constexpr FpsRanges kPrimaryRanges = {/*physical*/ k120_120Hz,
-                                          /*render*/ k120_120Hz};
-    constexpr FpsRanges kAppRequestRanges = {/*physical*/ k120_120Hz,
-                                             /*render*/ k0_120Hz};
-    EXPECT_EQ(SetPolicyResult::Changed,
-              selector.setDisplayManagerPolicy(
-                      {/*defaultMode*/ kModeId120, kPrimaryRanges, kAppRequestRanges}));
-
-    // Surface can translate to multiple layers in SF  scheduler due to category and frame rate
-    // value.
-    struct SurfaceVote {
-        // Params
-        std::string name = "";
-        Fps desiredFrameRate = 0_Hz;
-        LayerVoteType voteType = LayerVoteType::ExplicitDefault;
-        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
-        float weight = 1.f;
-    };
-
-    auto layers = createLayers(
-            std::initializer_list<SurfaceVote>{{.name = "60 fixed source",
-                                                .desiredFrameRate = 60_Hz,
-                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
-                                                .weight = 0.27f},
-                                               {.name = "1 fixed source + NoPreference",
-                                                .desiredFrameRate = 1_Hz,
-                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
-                                                .frameRateCategory =
-                                                        FrameRateCategory::NoPreference}});
-    auto actualRankedFrameRates = selector.getRankedFrameRates(layers);
-    EXPECT_EQ(60_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
-
-    layers = createLayers(
-            std::initializer_list<SurfaceVote>{{.name = "60 fixed source",
-                                                .desiredFrameRate = 60_Hz,
-                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
-                                                .weight = 0.27f},
-                                               {.name = "1 fixed source + Normal",
-                                                .desiredFrameRate = 1_Hz,
-                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
-                                                .frameRateCategory = FrameRateCategory::Normal}});
-    actualRankedFrameRates = selector.getRankedFrameRates(layers);
-    EXPECT_EQ(60_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
-
-    layers = createLayers(std::initializer_list<SurfaceVote>{
-            {.name = "30 fixed source + NoPreference",
-             .desiredFrameRate = 30_Hz,
-             .voteType = LayerVoteType::ExplicitExactOrMultiple,
-             .frameRateCategory = FrameRateCategory::NoPreference}});
-    actualRankedFrameRates = selector.getRankedFrameRates(layers);
-    EXPECT_EQ(30_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
-
-    layers = createLayers(std::initializer_list<SurfaceVote>{
-            {.name = "1 fixed source + NoPreference",
-             .desiredFrameRate = 1_Hz,
-             .voteType = LayerVoteType::ExplicitExactOrMultiple,
-             .frameRateCategory = FrameRateCategory::NoPreference}});
-    actualRankedFrameRates = selector.getRankedFrameRates(layers);
-    // Result affected by RefreshRateSelector.kMinSupportedFrameRate.
-    EXPECT_EQ(20_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
-
-    layers = createLayers(std::initializer_list<SurfaceVote>{
-            {.name = "24 fixed source + NoPreference",
-             .desiredFrameRate = 24_Hz,
-             .voteType = LayerVoteType::ExplicitExactOrMultiple,
-             .frameRateCategory = FrameRateCategory::NoPreference}});
-    actualRankedFrameRates = selector.getRankedFrameRates(layers);
-    EXPECT_EQ(24_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
-
-    layers = createLayers(std::initializer_list<SurfaceVote>{
-            {.name = "23.976 fixed source + NoPreference",
-             .desiredFrameRate = 23.976_Hz,
-             .voteType = LayerVoteType::ExplicitExactOrMultiple,
-             .frameRateCategory = FrameRateCategory::NoPreference}});
-    actualRankedFrameRates = selector.getRankedFrameRates(layers);
-    // Chooses 120 unless certain threshold is set, see tests test23976Chooses120 and
-    // test23976Chooses60IfThresholdIs120.
-    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
-}
-
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 45f86fa..59f0966 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -19,6 +19,8 @@
 #include <gmock/gmock.h>
 #include <optional>
 
+#include "Layer.h"
+
 namespace android::mock {
 
 class MockLayer : public Layer {
@@ -26,8 +28,11 @@
     MockLayer(SurfaceFlinger* flinger, std::string name)
           : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
 
-    MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> uid)
-          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, uid)) {}
+    MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> id)
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, id)) {}
+
+    MockLayer(SurfaceFlinger* flinger, std::optional<uint32_t> id)
+          : Layer(LayerCreationArgs(flinger, nullptr, "TestLayer", 0, {}, id)) {}
 
     explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
 
diff --git a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h
index fba4cd8..891f507 100644
--- a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h
@@ -54,6 +54,8 @@
     MOCK_METHOD(HalResult<aidl::android::hardware::power::ChannelConfig>, getSessionChannel,
                 (int tgid, int uid), (override));
     MOCK_METHOD(HalResult<void>, closeSessionChannel, (int tgid, int uid), (override));
+    MOCK_METHOD(HalResult<aidl::android::hardware::power::SupportInfo>, getSupportInfo, (),
+                (override));
 };
 
 } // namespace android::adpf::mock
\ No newline at end of file
diff --git a/vulkan/libvulkan/libvulkan_flags.aconfig b/vulkan/libvulkan/libvulkan_flags.aconfig
index 891bc02..dae5b52 100644
--- a/vulkan/libvulkan/libvulkan_flags.aconfig
+++ b/vulkan/libvulkan/libvulkan_flags.aconfig
@@ -8,3 +8,11 @@
   bug: "341978292"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "vulkan_1_4_instance_api"
+  namespace: "core_graphics"
+  description: "Enable support for the Vulkan 1.4 instance API"
+  bug: "370568136"
+  is_fixed_read_only: true
+}